
本文详解如何使用 Canvas 的径向渐变与混合模式(如 color-dodge 和 lighter)实现带颜色的动态聚光灯效果,替代纯黑白遮罩,让光照呈现暖色晕染、自然衰减的真实感。
本文详解如何使用 canvas 的径向渐变与混合模式(如 `color-dodge` 和 `lighter`)实现带颜色的动态聚光灯效果,替代纯黑白遮罩,让光照呈现暖色晕染、自然衰减的真实感。
在原方案中,开发者采用 destination-out 混合模式“挖空”圆形区域来模拟手电筒效果——这是一种基于遮罩的黑白高对比方式,虽简单但无法表现色彩、光晕或柔和过渡。若想让聚光灯具备真实光源质感(如暖黄光晕、中心高亮、边缘渐隐),需转向叠加式着色渲染:即先完整绘制图像,再用带颜色的径向渐变以加性混合模式(additive compositing)覆盖照射区域。
✅ 核心原理:渐变 + 加性混合
- createRadialGradient(x0, y0, r0, x1, y1, r1) 创建从圆心向外扩散的渐变,支持多色阶控制光色分布;
- globalCompositeOperation = "color-dodge" 强化高光与色彩饱和度,适合模拟强光中心;
- globalCompositeOperation = "lighter" 简单叠加亮度,增强边缘柔光感;
- globalAlpha 分层调控 可精细平衡不同混合模式的强度,避免过曝。
? 完整可运行示例代码
<canvas id="flashlight-canvas" style="border: 1px solid #333;"></canvas>
<script>
const canvas = document.getElementById("flashlight-canvas");
const ctx = canvas.getContext("2d");
let mouseX = 0, mouseY = 0;
let radius = 150;
let draw = () => {}; // 占位函数,待图片加载后赋值
// 创建并加载图像(注意:必须等待 load 事件!)
const image = new Image();
image.src = "https://3.api.artsmia.org/800/109833.jpg";
image.addEventListener("load", () => {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
draw = drawFlashlight; // 启用绘制逻辑
draw(); // 首帧渲染
}, { once: true });
// 构建彩色径向渐变(模拟暖调聚光灯)
const lightGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, radius);
lightGrad.addColorStop(0.0, "#ffdd99"); // 中心:明亮暖黄
lightGrad.addColorStop(0.3, "#ffb74d"); // 中段:琥珀橙
lightGrad.addColorStop(0.6, "#ff9800"); // 过渡:深橙
lightGrad.addColorStop(0.9, "#b76e00"); // 边缘:暗棕
lightGrad.addColorStop(1.0, "rgba(0,0,0,0)"); // 完全透明,实现自然衰减
function drawImage() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置变换
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(image, 0, 0);
}
function drawFlashlight() {
drawImage();
// 应用光照:平移坐标系至鼠标位置,绘制渐变圆
ctx.setTransform(1, 0, 0, 1, mouseX, mouseY);
ctx.fillStyle = lightGrad;
// 第一层:color-dodge 增强中心亮度与色调
ctx.globalAlpha = 0.8;
ctx.globalCompositeOperation = "color-dodge";
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
// 第二层:lighter 柔化边缘光晕
ctx.globalAlpha = 0.4;
ctx.globalCompositeOperation = "lighter";
ctx.fill();
}
// 绑定鼠标移动事件(含防抖建议见下方)
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
draw();
});
</script>⚠️ 关键注意事项
- 务必等待 image.onload:否则 naturalWidth/Height 为 0,Canvas 尺寸异常,绘图失败;
- 避免 destination-out 混合模式:它会擦除像素,无法叠加颜色;应全程使用 source-over / color-dodge / lighter 等叠加型模式;
- 渐变坐标系需配合 setTransform:将渐变原点锚定在 (mouseX, mouseY),而非画布左上角;
- 性能优化建议:高频 mousemove 可加节流(如 requestAnimationFrame 控制帧率),防止重绘过载;
- 移动端适配:需额外监听 touchmove 并转换 touches[0] 坐标。
✅ 效果升级方向
- 动态调整 radius 实现缩放/聚焦;
- 结合 shadowBlur 在光照边缘添加轻微发光;
- 使用 ctx.filter = "blur(2px)"(需开启 will-change: filter)实现更柔和光晕;
- 引入 HSL 渐变色停靠点,实现冷暖光切换动画。
通过上述方法,你不仅能实现彩色聚光灯,更能精准控制光色温度、衰减曲线与混合强度——让 Canvas 光效真正服务于视觉叙事,而非仅作技术演示。

















