1. 为什么选择Canvas与requestAnimationFrame在网页上实现动画效果有很多种方式比如CSS动画、GIF图片、SVG动画等。但如果你想要实现高性能、可定制化的复杂动画效果Canvas配合requestAnimationFrame绝对是首选组合。我做过不少网页动画项目实测下来这套方案在流畅度和可控性上表现最好。Canvas就像一块画布你可以用JavaScript在上面自由绘制任何图形。相比DOM操作Canvas的绘制性能要高得多特别适合处理大量动态元素比如几百片雪花。而requestAnimationFrame则是浏览器专门为动画设计的API它会根据屏幕刷新率自动调整回调频率保证动画流畅不卡顿。记得我第一次用setTimeout做动画时经常遇到画面撕裂、卡顿的问题。后来改用requestAnimationFrame后动画立刻变得丝滑流畅。这个经验让我深刻理解了为什么专业的前端动画都要用这个API。2. 从零开始搭建Canvas动画框架2.1 初始化Canvas画布首先我们需要在HTML中创建一个Canvas元素canvas idsnowCanvas/canvas然后在JavaScript中获取这个元素并设置正确的尺寸。这里有个关键点必须根据窗口大小动态调整Canvas尺寸否则在高分辨率屏幕上会出现模糊。const canvas document.getElementById(snowCanvas); const ctx canvas.getContext(2d); function resizeCanvas() { canvas.width window.innerWidth; canvas.height window.innerHeight; } // 初始设置 resizeCanvas(); // 窗口大小改变时重新设置 window.addEventListener(resize, resizeCanvas);2.2 创建动画循环传统的setTimeout/setInterval动画有个致命问题它们无法与屏幕刷新同步可能导致丢帧。requestAnimationFrame则完美解决了这个问题function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新和绘制所有雪花 updateSnowflakes(); drawSnowflakes(); requestAnimationFrame(animate); } // 启动动画 animate();这个循环每秒会运行60次取决于屏幕刷新率每次都会清除画布并重新绘制所有元素。我在项目中实测即使绘制500片雪花现代浏览器也能轻松保持60fps。3. 设计逼真的雪花效果3.1 雪花对象建模要让雪花看起来自然我们需要为每片雪花设计多个属性class Snowflake { constructor(canvasWidth, canvasHeight) { this.x Math.random() * canvasWidth; this.y Math.random() * -canvasHeight; // 从屏幕外开始 this.size Math.random() * 3 1; this.speed Math.random() * 1 0.5; this.wind Math.random() * 0.5 - 0.25; this.opacity Math.random() * 0.5 0.3; } }这些参数控制着雪花的大小、下落速度、飘动幅度和透明度。通过随机化这些值我们可以创造出更自然的雪景效果。我在实际项目中发现适当增加wind参数可以让雪花有左右飘动的效果看起来更加真实。3.2 雪花运动算法雪花的下落不是简单的匀速直线运动好的算法应该考虑重力加速度 - 雪花越下落越快风力影响 - 轻微的左右飘动旋转效果 - 雪花下落时的自转update() { this.y this.speed; this.x this.wind; // 添加一些随机扰动 if (Math.random() 0.95) { this.wind Math.random() * 0.5 - 0.25; } // 超出屏幕后重置到顶部 if (this.y canvas.height) { this.y Math.random() * -50; this.x Math.random() * canvas.width; } }4. 性能优化技巧4.1 对象池技术创建和销毁对象会产生内存开销。对于大量雪花我们可以使用对象池技术// 初始化对象池 const snowflakes Array(500).fill().map(() new Snowflake()); // 在动画循环中复用对象 function updateSnowflakes() { snowflakes.forEach(flake { flake.update(); if (flake.y canvas.height) { flake.reset(); // 重置位置而非创建新对象 } }); }4.2 分层渲染将静态背景和动态雪花分开渲染可以大幅提升性能!-- 背景层 -- canvas idbackground/canvas !-- 雪花层 -- canvas idsnowCanvas/canvas这样当雪花需要重绘时背景层不需要重新渲染。我在一个复杂场景中应用这个技巧后性能提升了约40%。4.3 自适应粒子数量根据设备性能动态调整雪花数量let targetFPS 60; let lastFrameTime performance.now(); let fps 60; function animate() { const now performance.now(); fps 0.9 * fps 0.1 * (1000 / (now - lastFrameTime)); lastFrameTime now; // 根据当前FPS调整雪花数量 if (fps 50 snowflakes.length 100) { snowflakes.pop(); } else if (fps 55 snowflakes.length 500) { snowflakes.push(new Snowflake()); } // ...其余动画逻辑 requestAnimationFrame(animate); }5. 进阶效果实现5.1 3D景深效果通过大小和速度差异模拟景深class Snowflake { constructor() { this.z Math.random() * 0.5 0.5; // 0.5-1.0之间的深度值 this.size this.z * 4; // 远处的雪花更小 this.speed this.z * 2; // 远处的雪花移动更慢 } draw(ctx) { // 根据深度调整透明度 ctx.globalAlpha this.z * 0.7; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } }5.2 交互效果让雪花对鼠标移动产生反应let mouseX 0; let mouseY 0; document.addEventListener(mousemove, (e) { mouseX e.clientX; mouseY e.clientY; }); class Snowflake { update() { // 计算与鼠标的距离 const dx mouseX - this.x; const dy mouseY - this.y; const distance Math.sqrt(dx * dx dy * dy); // 鼠标附近的雪花会被推开 if (distance 100) { this.x - dx * 0.01; this.y - dy * 0.01; } // 正常下落逻辑... } }6. 实际应用案例去年我为一家滑雪度假村网站实现了类似的雪景效果但做了一些定制化改进根据页面滚动速度调整雪花下落速度添加了雪花堆积效果在页面底部逐渐堆积实现了昼夜切换功能夜晚的雪花会微微发光核心代码结构是这样的class SnowScene { constructor() { this.snowflakes []; this.groundSnow []; // 存储堆积的雪花 this.nightMode false; } addGroundSnow(x, y, size) { this.groundSnow.push({x, y, size}); if (this.groundSnow.length 500) { this.groundSnow.shift(); } } draw() { // 绘制飘落的雪花 this.snowflakes.forEach(flake flake.draw()); // 绘制堆积的雪花 ctx.fillStyle this.nightMode ? rgba(200,230,255,0.8) : white; this.groundSnow.forEach(snow { ctx.beginPath(); ctx.arc(snow.x, snow.y, snow.size, 0, Math.PI * 2); ctx.fill(); }); } }这个项目让我深刻体会到好的动画效果不仅要技术过关更需要考虑用户体验。比如我们最初设计的雪花堆积效果太密集导致页面底部显得很脏后来调整了透明度和分布才达到理想效果。