)
原生JavaScript实现Canvas小鱼游动特效从零构建高性能交互式动画在网页设计中动态效果往往能显著提升用户体验。本文将带你从零开始用原生JavaScript和Canvas API实现一个高性能的小鱼游动特效包含3条智能游动的鱼儿与水面波浪的交互效果。不同于依赖jQuery等库的传统实现我们将专注于原生代码的性能优化与模块化设计。1. Canvas动画基础与环境搭建Canvas是HTML5提供的绘图API它通过JavaScript直接在网页上绘制图形。相比DOM操作Canvas更适合实现复杂的动画效果因为它避免了频繁的重排和重绘。首先创建基础HTML结构!DOCTYPE html html head meta charsetUTF-8 title小鱼游动特效/title style #fish-container { width: 100%; height: 200px; position: fixed; bottom: 0; left: 0; z-index: -1; opacity: 0.4; } /style /head body div idfish-container/div script srcfish-animation.js/script /body /html关键CSS设置说明position: fixed使动画固定在窗口底部z-index: -1确保内容显示在动画上方opacity控制透明度实现水纹效果2. 核心动画架构设计我们采用面向对象的方式组织代码主要包含三个核心类class FishAnimation { constructor(containerId) { this.container document.getElementById(containerId); this.canvas document.createElement(canvas); this.context this.canvas.getContext(2d); this.container.appendChild(this.canvas); // 初始化参数 this.fishes []; this.surfacePoints []; this.animationId null; this.lastTimestamp 0; // 绑定事件 window.addEventListener(resize, this.handleResize.bind(this)); this.canvas.addEventListener(mousemove, this.handleMouseMove.bind(this)); this.init(); } init() { this.setCanvasSize(); this.createSurface(); this.createFishes(); this.startAnimation(); } // 其他方法将在后续章节实现... }2.1 性能优化关键参数参数名默认值说明FISH_COUNT3鱼的数量POINT_INTERVAL5水面波浪点间隔(像素)WAVE_DAMPING0.9波浪阻尼系数FISH_SPEED2-5鱼游动速度范围MOUSE_INFLUENCE0.02鼠标影响系数3. 水面波浪效果实现水面效果基于弹簧质点模型每个点受相邻点影响形成波浪传播class SurfacePoint { constructor(animation, x) { this.animation animation; this.x x; this.baseHeight animation.height * 0.5; this.height this.baseHeight; this.velocity 0; this.force 0; } update() { // 弹簧力计算 const springForce 0.03 * (this.baseHeight - this.height); this.velocity springForce; this.velocity * 0.9; // 阻尼 this.height this.velocity; } interact(y, velocity) { const direction (this.height - y) 0 ? -1 : 1; this.velocity direction * Math.abs(velocity) * 0.01; } draw(ctx) { ctx.lineTo(this.x, this.animation.height - this.height); } }波浪传播原理每个点受弹簧力作用趋向基准位置相邻点通过力传递形成波动鼠标交互施加额外作用力4. 鱼类行为模拟每条鱼都有自己的运动逻辑和外观特征class Fish { constructor(animation) { this.animation animation; this.reset(); this.tailAngle 0; this.finAngle 0; } reset() { this.x Math.random() 0.5 ? -50 : this.animation.width 50; this.y Math.random() * this.animation.height; this.speed 2 Math.random() * 3; this.direction this.x 0 ? 1 : -1; this.amplitude 10 Math.random() * 20; this.frequency 0.05 Math.random() * 0.1; } update() { this.x this.speed * this.direction; // 正弦游动路径 const progress (this.x / this.animation.width) * Math.PI; this.y Math.sin(progress * this.frequency) * this.amplitude; // 边界检测 if ((this.direction 0 this.x this.animation.width 50) || (this.direction 0 this.x -50)) { this.reset(); } // 尾部摆动 this.tailAngle Math.sin(Date.now() * 0.005) * 0.5; this.finAngle Math.sin(Date.now() * 0.003) * 0.3; } draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(Math.PI Math.atan2( Math.sin(Date.now() * 0.005) * 2, this.direction )); // 鱼身绘制 ctx.beginPath(); ctx.moveTo(-20, 0); ctx.bezierCurveTo(-15, 10, 10, 8, 30, 0); ctx.bezierCurveTo(10, -8, -15, -10, -20, 0); ctx.fillStyle hsl(200, 70%, 50%); ctx.fill(); // 鱼尾绘制 ctx.save(); ctx.translate(30, 0); ctx.scale(0.9 0.1 * Math.sin(this.tailAngle), 1); ctx.beginPath(); ctx.moveTo(0, 0); ctx.quadraticCurveTo(5, 10, 15, 8); ctx.quadraticCurveTo(10, 0, 15, -8); ctx.quadraticCurveTo(5, -10, 0, 0); ctx.fill(); ctx.restore(); ctx.restore(); } }5. 动画循环与性能优化使用requestAnimationFrame实现高效动画循环class FishAnimation { // ...之前代码... startAnimation() { const animate (timestamp) { // 计算时间差实现帧率无关动画 const deltaTime timestamp - this.lastTimestamp; this.lastTimestamp timestamp; this.update(deltaTime); this.draw(); this.animationId requestAnimationFrame(animate); }; this.animationId requestAnimationFrame(animate); } update(deltaTime) { const scale deltaTime / 16; // 标准化到60fps // 更新水面点 this.surfacePoints.forEach(point point.update()); // 传播波浪力 for (let i 1; i this.surfacePoints.length; i) { const prev this.surfacePoints[i-1]; const curr this.surfacePoints[i]; const force 0.3 * (prev.height - curr.height); prev.velocity - force * scale; curr.velocity force * scale; } // 更新鱼位置 this.fishes.forEach(fish fish.update()); } draw() { const ctx this.context; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 绘制鱼 this.fishes.forEach(fish fish.draw(ctx)); // 绘制水面 ctx.save(); ctx.globalCompositeOperation xor; ctx.fillStyle hsl(200, 50%, 70%); ctx.beginPath(); ctx.moveTo(0, this.canvas.height); this.surfacePoints.forEach(point point.draw(ctx)); ctx.lineTo(this.canvas.width, this.canvas.height); ctx.closePath(); ctx.fill(); ctx.restore(); } handleResize() { this.setCanvasSize(); this.createSurface(); } handleMouseMove(e) { const rect this.canvas.getBoundingClientRect(); const x e.clientX - rect.left; const y e.clientY - rect.top; // 找到最近的水面点施加影响 const index Math.round(x / this.pointInterval); if (index 0 index this.surfacePoints.length) { this.surfacePoints[index].interact(y, e.movementY); } } }性能提示在动画循环中避免频繁创建对象尽量复用变量。使用transform代替直接坐标计算可以提升渲染性能。6. 完整实现与参数调优将所有部分组合起来并添加配置参数// 初始化动画 const animation new FishAnimation(fish-container, { fishCount: 3, pointInterval: 5, waveDamping: 0.9, mouseInfluence: 0.02 }); // 可调参数示例 class FishAnimation { constructor(containerId, options {}) { this.config { fishCount: options.fishCount || 3, pointInterval: options.pointInterval || 5, waveDamping: options.waveDamping || 0.9, mouseInfluence: options.mouseInfluence || 0.02, waveSpeed: options.waveSpeed || 0.3 }; // ...其余初始化代码... } createSurface() { const count Math.round(this.canvas.width / this.config.pointInterval); this.pointInterval this.canvas.width / (count - 1); this.surfacePoints []; for (let i 0; i count; i) { const point new SurfacePoint(this, i * this.pointInterval); if (i 0) { point.prev this.surfacePoints[i-1]; this.surfacePoints[i-1].next point; } this.surfacePoints.push(point); } } createFishes() { this.fishes []; for (let i 0; i this.config.fishCount; i) { this.fishes.push(new Fish(this)); } } // ...其余方法... }7. 高级技巧与扩展思路性能监控添加FPS计数器检测性能let fps 0; let lastFpsUpdate 0; let frameCount 0; const updateFps (timestamp) { frameCount; if (timestamp - lastFpsUpdate 1000) { fps frameCount; frameCount 0; lastFpsUpdate timestamp; console.log(FPS: ${fps}); } };响应式设计根据设备性能动态调整鱼的数量handleResize() { this.setCanvasSize(); this.createSurface(); // 根据屏幕尺寸调整鱼数量 const area this.canvas.width * this.canvas.height; const targetCount Math.min( 5, Math.floor(area / (250 * 250) * this.config.fishCount) ); while (this.fishes.length targetCount) { this.fishes.pop(); } while (this.fishes.length targetCount) { this.fishes.push(new Fish(this)); } }视觉效果增强添加水花溅起效果class Splash { constructor(x, y, power) { this.particles []; for (let i 0; i 10; i) { this.particles.push({ x, y, vx: (Math.random() - 0.5) * power, vy: -Math.random() * power * 0.5, size: 1 Math.random() * 3, life: 30 Math.random() * 20 }); } } update() { this.particles.forEach(p { p.x p.vx; p.y p.vy; p.vy 0.1; // 重力 p.life--; }); return this.particles.some(p p.life 0); } draw(ctx) { ctx.save(); ctx.fillStyle hsla(200, 100%, 70%, 0.7); this.particles.forEach(p { if (p.life 0) { ctx.globalAlpha p.life / 50; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } }); ctx.restore(); } }这个350行左右的实现完整呈现了一个高性能的Canvas动画特效包含了面向对象设计、物理模拟、用户交互等核心要素。通过模块化的代码结构你可以轻松扩展更多功能如添加不同鱼种、实现食物追逐行为或增加更复杂的水面效果。