如何在JavaScript中为各类形状添加带混合模式的平滑内阴影?
实现带混合模式的平滑内阴影(复刻Photoshop效果)
太懂这种被一个视觉效果逼到崩溃的感觉了!我之前也在复刻Photoshop内阴影的时候踩过XOR的坑,边缘锯齿真的让人抓狂。其实换个思路,用离屏Canvas+裁剪的方法,就能完美实现带任意混合模式的平滑内阴影,而且对所有形状都适用。
核心思路
XOR合成之所以会破坏边缘平滑度,是因为它直接对像素做异或运算,会丢失半透明的反锯齿像素。而我们可以利用原生阴影的平滑特性,通过以下步骤把外阴影“转化”为内阴影:
- 在离屏Canvas上生成形状的反向偏移外阴影(偏移方向和内阴影相反)
- 回到主Canvas,用目标形状作为裁剪区域
- 将离屏的阴影绘制到裁剪区域内,同时应用需要的混合模式
这种方法既保留了shadowBlur原生的平滑反锯齿,又能支持所有Canvas的混合模式。
完整代码实现
下面是一个通用的内阴影绘制函数,支持任意形状和混合模式:
function drawInnerShadow(ctx, shapePath, shadowOptions) { // 解构阴影参数,默认值兜底 const { x = 3, y = 3, blur = 5, color = 'rgba(0,0,0,0.5)', blendMode = 'normal' } = shadowOptions; // 创建与主画布同尺寸的离屏Canvas const offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = ctx.canvas.width; offscreenCanvas.height = ctx.canvas.height; const offCtx = offscreenCanvas.getContext('2d'); // 步骤1:在离屏Canvas生成反向偏移的外阴影 offCtx.save(); offCtx.shadowColor = color; offCtx.shadowBlur = blur; // 反向偏移:外阴影向外,内阴影向内,所以取反偏移量 offCtx.shadowOffsetX = -x; offCtx.shadowOffsetY = -y; // 绘制透明形状,只保留阴影 offCtx.fillStyle = 'transparent'; offCtx.fill(shapePath); offCtx.restore(); // 步骤2:回到主Canvas绘制内阴影 ctx.save(); // 先绘制原形状(确保裁剪区域基于这个形状) ctx.fill(shapePath); // 设置裁剪区域:只有形状内部的区域会被绘制 ctx.clip(); // 应用指定的混合模式 ctx.globalCompositeOperation = blendMode; // 绘制离屏的阴影,此时阴影只会显示在裁剪区域内(即形状内部) ctx.drawImage(offscreenCanvas, 0, 0); // 恢复画布状态 ctx.restore(); } // ------------------------------ // 使用示例:带Overlay混合模式的圆角矩形内阴影 // ------------------------------ const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); canvas.width = 300; canvas.height = 250; // 创建圆角矩形路径(支持任意Path2D形状) const rectPath = new Path2D(); rectPath.roundRect(50, 50, 200, 150, 20); // 1. 先绘制形状的基础底色 ctx.fillStyle = '#f5f5f5'; ctx.fill(rectPath); // 2. 绘制内阴影(参数可自定义) drawInnerShadow(ctx, rectPath, { x: 6, y: 6, blur: 12, color: 'rgba(0,0,0,0.6)', blendMode: 'overlay' });
关键优势
- 完美平滑边缘:利用原生
shadowBlur生成阴影,反锯齿完全保留,不会出现XOR方法的边缘瑕疵 - 全混合模式支持:直接通过
globalCompositeOperation设置,支持overlay、multiply、screen等所有Canvas混合模式 - 适配任意形状:只要能创建
Path2D对象的图形(矩形、圆形、自定义路径等)都能适用 - 性能友好:仅使用一次离屏Canvas绘制,不会有过多的图层合成开销
注意事项
- 离屏Canvas的尺寸必须和主Canvas一致,避免缩放导致的阴影模糊
- 如果目标形状是描边样式,裁剪时需要考虑描边宽度(可以用
Path2D的addPath结合ctx.lineWidth调整路径范围) - 阴影偏移量的方向:内阴影想向左上方偏移,就把
x和y设为正数(因为代码里会取反生成反向外阴影)
内容的提问来源于stack exchange,提问作者Tyler John
相关产品推荐
相关产品推荐

