显示带有阴影效果的底部 canvas - 裁剪区域内的阴影

Display bottom canvas with a shadow effect - shadow within clipped area

我是 canvas 的新手,感谢您的耐心等待。

我编写了一个引擎,它在 2 个 canvas 元素中创建了 2 个不同的层,这些元素层层叠叠。它们包含一些生成的图片,这里不重要。

我正在尝试创建一种效果,当我将鼠标移到顶层上并单击时将显示底层。

像这样:

这是我目前尝试过的方法:

  1. 在 canvas 元素上使用透明度并显示底部 canvas(快速但不可用)
  2. 重新创建剪辑区域。
    每当我按下鼠标时,我都会存储当前坐标并使用更新的剪辑区域
  3. 重新渲染 canvas


如果我使用描边创建阴影,更新剪辑区域会很慢 + 我不确定如何从中删除线条(见图)。

如果我去掉阴影效果,效果会非常快,但我需要它。

我唯一想到的是如何加快速度,就是保存每次点击的坐标,然后将其重新计算为 1 个形状并在其上投下阴影 - 我仍然会有线条,但它会更快,因为不会画上千个圆...

任何帮助将不胜感激!

您可以利用浏览器的内置插值,将其用作伪低通滤波器,但首先将其涂成黑色:

  • 将顶层复制到底层
  • 设置源输入补偿。模式
  • 全黑
  • 设置源输入补偿。模式
  • 将图像缩小到 25%
  • 将 25% 的区域缩放回原来的 50%(或当前的两倍)
  • 将现在 50% 的区域缩放回原来的 100%。会模糊。

根据您想要的模糊程度,您可以添加额外的步骤。话虽这么说:模糊阴影无论如何扭曲都是一个密集的操作。可以做出妥协,例如仅渲染鼠标上的阴影(如下面的演示所示)。

例子

使用两层的示例。顶层让你画任何东西,底层会在稍后绘制时在底部显示阴影版本。

var ctx = document.getElementById("top").getContext("2d"),
    bctx = document.getElementById("bottom").getContext("2d"),
    bg = new Image(),
    isDown = false;

bg.src = "http://i.imgur.com/R2naCpK.png";

ctx.fillStyle = "#27f";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "destination-out";  // "eraser"

ctx.canvas.onmousedown = function(e) {isDown = true};

window.onmousemove = function(e) {
  if (!isDown) return;
  var pos = getPos(ctx.canvas, e);
  ctx.beginPath();
  ctx.moveTo(pos.x + 10, pos.y);
  ctx.arc(pos.x, pos.y, 10, 0, 2*Math.PI);         // erase while drawing
  ctx.fill();  
};

window.onmouseup = function(e) {
  if (isDown) {
    isDown = false;
    makeShadow();
  }
};

function makeShadow(){
  var w = bctx.canvas.width,
      h = bctx.canvas.height,
      offset = 7, 
      alpha = 0.75;
  
  // reset alpha
  bctx.globalAlpha = 1;

  // normal comp mode to clear as it is faster than using "copy"
  bctx.globalCompositeOperation = "source-over";
  bctx.clearRect(0, 0, w, h);

  // copy top-layer to bottom-layer
  bctx.drawImage(ctx.canvas, 0, 0);
  
  // comp. mode will only draw in to non-alpha pixels next
  bctx.globalCompositeOperation = "source-in";
  
  // black overlay
  bctx.fillRect(0, 0, w, h);
  
  // copy mode so we don't need an extra canvas
  bctx.globalCompositeOperation = "copy";
  
  // step 1: reduce to 50% (quality related - create more steps to increase blur/quality)
  bctx.drawImage(bctx.canvas, 0, 0, w, h, 0, 0, w * 0.5, h * 0.5);
  
  bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, 0, 0, w * 0.25, h * 0.25);
  bctx.drawImage(bctx.canvas, 0, 0, w * 0.25, h * 0.25, 0, 0, w * 0.5, h * 0.5);
  
  // shadow transparency
  bctx.globalAlpha = alpha;
  
  // step 2: draw back up to 100%, draw offset
  bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, offset, offset, w, h);

  // comp in background image
  bctx.globalCompositeOperation = "destination-over";
  bctx.drawImage(bg, 0, 0, w, h);
}

function getPos(canvas, e) {
  var r = canvas.getBoundingClientRect();
  return {x: e.clientX - r.left, y: e.clientY - r.top};
}
div {position:relative;border:1px solid #000;width:500px;height:500px}
canvas {position:absolute;left:0;top:0}
#bottom {background:#eee}
<div>
  <canvas id="bottom" width=500 height=500></canvas>
  <canvas id="top" width=500 height=500></canvas>
</div>