Canvas绘制雪花前必须设置width/height原生属性而非CSS样式,否则坐标错乱;需用requestAnimationFrame平滑动画、半透明覆盖清屏、贝塞尔曲线画六角雪花,并优化移动端触摸和随机数性能。

canvas 绘制雪花前必须设置宽高属性
直接写 <canvas> 标签但没设 width 和 height,雪花会挤成一团或根本不动——浏览器默认 canvas 是 300×150 像素,且 CSS 缩放不改变绘图坐标系,导致所有 ctx.moveTo() 坐标全错乱。
正确做法是:用 JS 动态读取容器尺寸并赋给 canvas 的原生属性(不是 CSS):
const canvas = document.getElementById('snow');
const ctx = canvas.getContext('2d');
// 必须这样设
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;- 别用
canvas.style.width = '100%'替代,它只缩放显示,不扩大绘图区域 - 监听
resize事件时,要重新赋值canvas.width/height,否则雪花会拉伸变形 - 移动端需注意:
window.innerWidth可能包含地址栏高度变化,可加防抖
用 requestAnimationFrame 实现平滑飘落而非 setInterval
setInterval 容易掉帧、雪花忽快忽慢;requestAnimationFrame 跟屏幕刷新率同步,飘落更自然,且页面切后台时自动暂停,省资源。
关键点在于每次重绘前清空画布,但别用 ctx.clearRect(0, 0, width, height) 全清——那样雪花会闪烁。改用半透明覆盖模拟“拖尾”效果:
立即学习“前端免费学习笔记(深入)”;
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
- 透明度控制在
0.05–0.2之间,太大会糊成一片,太小则无拖尾感 - 每个雪花对象需独立存储
x、y、speed、size、drift(左右晃动幅度),避免全部同步下落 - 雪花移出画布底部后,重置到顶部并随机 x 坐标,别直接
delete再push新对象,容易 GC 波动
用贝塞尔曲线画真实感雪花而非圆点
纯 ctx.arc(x, y, size, 0, Math.PI * 2) 就是白点,缺乏层次。真正轻盈的雪花得有分叉和旋转——用 ctx.save()/ctx.restore() 配合 ctx.rotate() 画六角对称结构:
function drawSnowflake(ctx, x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(Math.random() * Math.PI * 2);
for (let i = 0; i < 6; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -size);
ctx.stroke();
ctx.rotate(Math.PI / 3);
}
ctx.restore();
}- 每片雪花调用前先
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.random() * 0.7 + 0.3})`,模拟远近虚实 - 避免在循环里反复调用
ctx.beginPath()—— 性能差;把六条线合并进一个 path 更高效 - 别用
shadowBlur模拟光晕,低端设备卡顿明显;改用多层不同透明度的相同雪花微偏移绘制
移动端 touch 设备需禁用默认滚动干扰
手指划过 canvas 区域时,页面可能意外滚动或缩放,雪花动画卡顿。必须显式阻止:
canvas.addEventListener('touchstart', e => e.preventDefault(), { passive: false });
canvas.addEventListener('touchmove', e => e.preventDefault(), { passive: false });-
{ passive: false }不可省略,否则 iOS Safari 仍会滚动 - 若 canvas 是全屏背景,建议加
style="touch-action: none"到 canvas 标签上,双重保险 - 部分安卓 WebView 对
passive: false报 warning,但功能正常,可忽略
雪花数量超过 200 片时,Math.random() 在低端机上会成为瓶颈,改用预生成的随机数数组或 crypto.getRandomValues() 分批填充更稳。



















