Canvas无法直接实现径向模糊,需用多层渐变透明同心圆模拟光圈效果:从中心向外扩展、半径增大、透明度衰减,并严格适配DPR与禁用插值以保边缘锐利。

Canvas本身不支持直接的“径向模糊”滤镜,但可以通过逐帧绘制多层缩放/渐变透明的同心圆来模拟相机光圈开合时的视觉扩散效果——这本质上是用大量带透明度的圆形叠加,营造出由中心向外辐射、逐渐虚化的感觉。
这种实现方式不是GPU加速的真模糊,而是视觉欺骗式光圈动画,轻量、可控、兼容性好,适合加载页、转场或UI反馈场景。
核心思路:用同心圆堆叠代替径向模糊
浏览器没有 ctx.radialBlur() 这样的 API。所谓“光圈效果”,关键在于两点:
- 所有图形从同一中心点(如画布中心)向外扩展
- 越靠外的环越透明、越大、颜色略淡,形成“发散+衰减”感
因此,不需要高斯模糊算法,只需在每帧中:
- 生成若干个新圆(位置固定,半径小,不透明)
- 让已有圆持续增大半径 + 降低透明度
- 透明度归零时移除该圆
避免模糊的关键:适配设备像素比(DPR)
如果光圈边缘看起来毛糙、发虚,大概率不是算法问题,而是 Canvas 在高清屏上被错误缩放。必须做 DPR 适配:
- 获取
window.devicePixelRatio - 将 canvas 元素的
width/height设为css尺寸 × dpr - 用
ctx.scale(dpr, dpr)统一坐标系,确保 1px 线条对应 1 物理像素 - 禁用插值缩放:
canvas.style.imageRendering = 'crisp-edges'
否则,哪怕只是画一个圆,高 DPI 屏上也会因浏览器双线性插值而边缘糊掉。
提升真实感的三个细节技巧
纯随机圆容易显得杂乱。加一点控制,更像专业镜头光圈:
-
起始半径微调:不要都从 r=1 开始,可设为
r = 3 + Math.random() * 2,避免所有圆同步“炸开” -
透明度衰减非线性:用
c4 *= 0.985而非减固定值,让淡出更自然 -
色彩倾向控制:光圈常带暖调,可让
c1(红)初始值偏高,c2(绿)略低,c3(蓝)最低,模拟镀膜反光
简化版可运行代码结构(含 DPR 修复)
以下精简逻辑,去掉了原始示例中易引发内存泄漏的 splice 遍历删除,改用 filter:
const canvas = document.getElementById('c1');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
ctx.scale(dpr, dpr);
canvas.style.imageRendering = 'crisp-edges';
<p>const circles = [];
setInterval(() => {
// 每 300ms 新增一个光圈起点
if (Math.random() > 0.7) {
circles.push({
x: rect.width / 2,
y: rect.height / 2,
r: 2 + Math.random() <em> 3,
c1: 255,
c2: 200 + Math.random() </em> 30,
c3: 160 + Math.random() * 20,
c4: 0.9
});
}</p><p>// 清屏 → 更新 → 重绘
ctx.clearRect(0, 0, rect.width, rect.height);
for (let i = 0; i < circles.length; i++) {
const c = circles[i];
c.r += 0.8;
c.c4 <em>= 0.982;
}
circles.forEach(c => {
ctx.beginPath();
ctx.fillStyle = <code>rgba(${c.c1},${c.c2},${c.c3},${c.c4})</code>;
ctx.arc(c.x, c.y, c.r, 0, Math.PI </em> 2);
ctx.fill();
});</p><p>// 清理已透明的圆
circles.splice(0, circles.findIndex(c => c.c4 < 0.05) + 1);
}, 1000 / 60);</p>不复杂但容易忽略。


















