
本文介绍如何使用canvas的径向渐变与混合模式(如color-dodge、lighter)实现带颜色的手电筒光照效果,替代纯黑底挖洞式遮罩,使光照呈现自然色温与辉光。
本文介绍如何使用canvas的径向渐变与混合模式(如color-dodge、lighter)实现带颜色的手电筒光照效果,替代纯黑底挖洞式遮罩,使光照呈现自然色温与辉光。
在原实现中,手电筒效果依赖 destination-out 复合操作——即在图像上“挖空”一个圆形区域,露出背景(通常是黑色),本质上是去色遮罩,无法体现光照本身的色彩属性。若想模拟真实光源(如暖黄灯、冷白光或霓虹灯效),需转向叠加式着色光照:先绘制原始图像,再以彩色径向渐变覆盖指定区域,并通过高级混合模式增强亮度与色彩渗透感。
✅ 核心技术要点
-
径向渐变(
createRadialGradient):定义从中心到边缘的颜色过渡,模拟光强衰减; -
混合模式(
globalCompositeOperation):-
"color-dodge":提亮并保留色调,适合主光效; -
"lighter":简单叠加亮度,增强辉光层次;
-
-
动态坐标变换(
setTransform):将渐变原点锚定于鼠标位置,避免每次重绘渐变对象; -
图像加载保障:必须等待
Image.onload后再设置 canvas 尺寸与启动渲染,否则naturalWidth/Height为 0,导致渲染异常。
? 完整可运行示例
<canvas id="canvas" width="800" height="600"></canvas>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const image = new Image();
// 使用更富表现力的暖色渐变(可按需调整为蓝、紫、橙等)
const lightGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, 150);
lightGrad.addColorStop(0.0, "#ffeedd"); // 中心:柔和米白
lightGrad.addColorStop(0.3, "#ffcc88"); // 中段:暖橙
lightGrad.addColorStop(0.7, "#aa7733"); // 边缘:深棕过渡
lightGrad.addColorStop(1.0, "rgba(0,0,0,0)"); // 完全透明,自然消散
let mouseX = canvas.width / 2;
let mouseY = canvas.height / 2;
let radius = 150;
image.src = "https://3.api.artsmia.org/800/109833.jpg";
image.onload = () => {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
draw(); // 首帧渲染
};
function drawImage() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(image, 0, 0);
}
function draw() {
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.3;
ctx.globalCompositeOperation = "lighter";
ctx.beginPath();
ctx.arc(0, 0, radius * 1.4, 0, Math.PI * 2);
ctx.fill();
}
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
draw();
});
// 可选:支持触摸设备
canvas.addEventListener("touchmove", (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
mouseX = touch.clientX - rect.left;
mouseY = touch.clientY - rect.top;
draw();
});⚠️ 注意事项与优化建议
-
性能提示:避免在
mousemove中重复创建radialGradient(已在全局创建一次),仅重用即可; -
响应式适配:若页面缩放或 canvas 尺寸变化,请监听
resize并重新计算getBoundingClientRect(); -
移动端兼容性:务必添加
touchmove事件并调用e.preventDefault()防止页面滚动干扰; -
色彩调试技巧:推荐使用 HSL 色值(如
hsl(30, 100%, 70%))调节色相/饱和度,比 RGB 更直观; -
渐变平滑度:增加
colorStop节点(如 5–7 个)可获得更细腻的光晕过渡。
通过上述方法,你不再局限于“照亮黑暗”,而是真正赋予手电筒以物理可信的光学特性——它不再是简单的圆形镂空,而是一束有温度、有衰减、有氛围的光。


















