Web编程技术营地
研究、演示、创新

阴影

撰写时间:2025-11-20

修订时间:2025-11-21

基本用法

绘制阴影的本质,是在指定的偏移位置,用指定的颜色,重绘当前图形。

ctx.shadowColor = 'gray'; ctx.shadowOffsetX = 10; ctx.shadowOffsetY = 10; ctx.shadowBlur = 5; let x = 20, y = 20; let width = 60, height = 60; stroke(); y += 90; fill(); function stroke() { ctx.strokeStyle = 'orange'; ctx.strokeRect(x, y, width, height); ctx.font = '64px Helvetica'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.strokeStyle = 'white'; ctx.strokeText('Canvas Shadow', x + 100, y + height / 2); } function fill() { ctx.fillStyle = 'orange'; ctx.fillRect(x, y, width, height); ctx.font = '64px Helvetica'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillStyle = 'white'; ctx.fillText('Canvas Shadow', x + 100, y + height / 2); }

shadowBlur的值为0时,将不应用任何模糊效果,此时可清晰地看到绘制阴影线条的效果。随着该值的增大,模糊度越来越高,阴影线条越来越模糊,且阴影线条的宽度来越来越大,直至产生背景雾化的效果。一般情况下,该值不要设得太高。

描边与填充

各种组合效果

ctx.strokeStyle = '#CCC'; ctx.fillStyle = 'hsl(180, 50%, 30%)'; ctx.globalCompositeOperation = "source-over"; ctx.shadowColor = 'orange'; ctx.lineWidth = 1; drawShadows();
ctx.shadowOffsetX = 12; ctx.shadowOffsetY = 12; ctx.shadowBlur = 5; let x = 80, y = 30; let halfWidth = 60, height = 130; const offsetX = 160; function drawShadows() { stroke(); x += offsetX; fill(); x += offsetX; strokeAndFill(); x += offsetX; fillAndStroke(); } function stroke() { beginPath(); ctx.stroke(); } function fill() { beginPath(); ctx.fill(); } function strokeAndFill() { beginPath(); ctx.stroke(); ctx.fill(); } function fillAndStroke() { beginPath(); ctx.fill(); ctx.stroke(); } function beginPath() { ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x - halfWidth, y + height); ctx.lineTo(x + halfWidth, y + height); ctx.closePath(); }

1个图形只描边,第2个图形只填充,第3个图形先描边再填充,第4个图形先填充再描边。

阴影效果仅在globalCompositeOperation值为source-over时才有效。

先描边再填充时,右边及底边的白色边框缺失。

先填充,再描边,将导致绿色填充颜色中混有描边的阴影,有点像钢化玻璃的效果,但不够真实。

凹陷效果

加大线条宽度,并将阴影颜色改为黑色。

ctx.strokeStyle = '#CCC'; ctx.fillStyle = 'hsl(180, 50%, 30%)'; ctx.globalCompositeOperation = "source-over"; ctx.shadowColor = '#000'; ctx.lineWidth = 5; drawShadows();
ctx.shadowOffsetX = 12; ctx.shadowOffsetY = 12; ctx.shadowBlur = 5; let x = 80, y = 30; let halfWidth = 60, height = 130; const offsetX = 160; function drawShadows() { stroke(); x += offsetX; fill(); x += offsetX; strokeAndFill(); x += offsetX; fillAndStroke(); } function stroke() { beginPath(); ctx.stroke(); } function fill() { beginPath(); ctx.fill(); } function strokeAndFill() { beginPath(); ctx.stroke(); ctx.fill(); } function fillAndStroke() { beginPath(); ctx.fill(); ctx.stroke(); } function beginPath() { ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x - halfWidth, y + height); ctx.lineTo(x + halfWidth, y + height); ctx.closePath(); }

此时可清晰地看到先描边再填充所导致白色边框缺失的原因:被后续填充的阴影覆盖了。

而第4个图形先填充再描边的凹陷效果非常真实。

可见,在应用阴影时,适当加大线条的宽度值,可取得较为理想的凹陷效果。

浮雕效果

可利用先描边再填充的特性,加大线条的宽度值,让边框大到足够完全承载背景的内容,则可制作浮雕效果。

ctx.strokeStyle = '#CCC'; ctx.fillStyle = 'hsl(180, 50%, 30%)'; ctx.globalCompositeOperation = "source-over"; ctx.shadowColor = '#000'; ctx.lineWidth = 20; strokeAndFill();
ctx.shadowOffsetX = 12; ctx.shadowOffsetY = 12; ctx.shadowBlur = 5; let x = 100, y = 40; let halfWidth = 60, height = 130; const offsetX = 160; function drawShadows() { strokeAndFill(); } function stroke() { beginPath(); ctx.stroke(); } function fill() { beginPath(); ctx.fill(); } function strokeAndFill() { beginPath(); ctx.stroke(); ctx.fill(); } function fillAndStroke() { beginPath(); ctx.fill(); ctx.stroke(); } function beginPath() { ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x - halfWidth, y + height); ctx.lineTo(x + halfWidth, y + height); ctx.closePath(); }

白色底板是空心的,但由于描边时是沿各边框线左右(或上下)各取边框线的宽度的一半而绘制,故填充的边界线正好位于白色边框线的中间,从而完全遮挡住空心的部分,从而营造出中间部位凸起的效果。

像框效果

ctx.lineWidth = 6; let x = 50, y = 25, width = 200, height = 150; ctx.fillStyle = "sienna"; ctx.fillRect(x, y, width, height); ctx.strokeStyle = "orange"; ctx.strokeRect(x, y, width, height); x = 300; ctx.fillRect(x, y, width, height); ctx.shadowColor = "transparent"; ctx.strokeRect(x, y, width, height);
ctx.shadowColor = "#000"; ctx.shadowOffsetX = 15; ctx.shadowOffsetY = 15; ctx.shadowBlur = 8;

填充阴影及描边阴影可以分别控制。在绘制右边的图形时,填充时有阴影,描边时将阴影颜色设置为透明。

对于左边的效果,仍有一点缺陷:阳光从左上方照射,像框左上角内部已有阴影,但像框右下角内部应有高光出现,效果才真实。可通过裁切 (clip),再单独绘制右下角高光的效果。

ctx.lineWidth = 10; let x = 50, y = 25, width = 200, height = 150; ctx.fillStyle = "sienna"; ctx.fillRect(x, y, width, height); ctx.strokeStyle = "orange"; ctx.strokeRect(x, y, width, height); drawRightBottemInnerset(); function drawRightBottemInnerset() { ctx.save(); ctx.beginPath(); ctx.moveTo(x, y + height); ctx.lineTo(x + width, y + height); ctx.lineTo(x + width, y); ctx.closePath(); ctx.clip(); ctx.shadowColor = "#FFFC"; ctx.shadowOffsetX = -ctx.shadowOffsetX / 2 + 0; ctx.shadowOffsetY = -ctx.shadowOffsetY / 2 + 2; ctx.strokeRect(x, y, width, height); ctx.restore(); }
ctx.shadowColor = "#000"; ctx.shadowOffsetX = 15; ctx.shadowOffsetY = 15; ctx.shadowBlur = 8;

通过裁切,将像框右下角设为有效绘制区域再往左上的方向绘制高光。代表高光的阴影颜色使用了alpha通道,可避免出现过于生硬的光照效果。根据实际效果再微调两个偏移值。

由于颜色混合模式默认为source-over,效果为直接覆盖的关系,因此先简单地绘制全局效果,最后再叠加一层右下角效果即可,可省去单独精细绘制左上角的步骤。

仍有一点小瑕疵:在高光影响之下,像框的右侧及底侧自身的颜色有些许发亮。

参考资源

  1. Canvas Shadows