如何用单个JavaScript文件为多个Canvas应用相同交互效果?
解决多Canvas粒子交互的问题(单JS文件实现)
嘿,作为刚摸Canvas的新手,你碰到的问题核心是没做好多个Canvas的实例隔离——原代码里把所有Canvas的上下文、粒子都共用了全局变量,肯定会出问题!咱完全可以用单个JS文件搞定,还能支持任意数量的Canvas,下面一步步给你修正:
先说说原代码的几个关键问题
- 全局变量冲突:你用了全局的
canvas、context和net数组,但每个Canvas需要自己独立的上下文和粒子集合,混在一起只会让所有Canvas显示一样的内容还乱套 - Canvas选择错误:初始化时写的
document.getElementById("canvas#dot_"+j)是错的,ID是dot_j,直接用dot_${j}就行,不需要加canvas# - 按钮类名拼写错:HTML里按钮的类是
color_chane,但JS里写的color_change,导致点击事件根本绑定不上 - Dot类依赖全局:Dot类里直接用全局的
canvas/context,没有和具体的Canvas关联,粒子没法正确对应到各自的画布
修正后的完整实现
HTML部分(仅修正按钮类名拼写)
<body> <div class="container" style="display: flex;"> <div class="buttons"> <a href="#" class="color_change">Button 1</a> <canvas id="dot_1"></canvas> </div> <div class="buttons"> <a href="#" class="color_change">Button 2</a> <canvas id="dot_2"></canvas> </div> <div class="buttons"> <a href="#" class="color_change">Button 3</a> <canvas id="dot_3"></canvas> </div> </div> </body>
JavaScript部分(单文件搞定,支持任意数量Canvas)
// 全局颜色配置与索引(所有Canvas同步切换) let index = localStorage.getItem('index') ? parseInt(localStorage.getItem('index')) : 0; const colorSets = { first: ["#4B2F73", "#8C0335", "#21EE03", "#011526", "#2960A4", "#0F0140", "#8C0444"], second: ["#3B2559", "#021E73", "#26C013", "#00070D", "#3E86C0", "#060126", "#591547"], third: ["#0A2B40", "#0442BF", "#29A619", "#024059", "#11C6D8", "#0D05F2", "#101C40"], fourth: ["#71C6D9", "#034C8C", "#737373", "#024959", "#BDA092", "#060273", "#011526"], fifth: ["#5FCDD9", "#023059", "#282828", "#3CA692", "#D83911", "#0ABF04", "#F20505"] }; // 粒子类:现在接收对应的Canvas上下文作为参数,不再依赖全局 class Dot { constructor(context) { this.context = context; this.canvas = context.canvas; this.x = 0; this.y = 0; this.radius = 0; this.direction = 0; this.speed = 0; } getFillStyle() { const canvasWidth = this.canvas.width; if (this.x > 4 * canvasWidth / 5) return colorSets.fifth[index]; if (this.x > 3 * canvasWidth / 5) return colorSets.fourth[index]; if (this.x > 2 * canvasWidth / 5) return colorSets.third[index]; if (this.x > canvasWidth / 5) return colorSets.second[index]; return colorSets.first[index]; } update() { const dir = Math.PI * this.direction / 180; this.y += Math.sin(dir) * this.speed; this.x += Math.cos(dir) * this.speed; } draw() { const ctx = this.context; ctx.beginPath(); ctx.fillStyle = this.getFillStyle(); ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); ctx.fill(); } setup() { this.x = Math.random() * this.canvas.width; this.y = Math.random() * this.canvas.height; this.speed = Math.random() * 1.5; this.direction = Math.random() * 360; this.radius = Math.random() * (this.canvas.width / this.canvas.height) * 0.15; } } // 单个Canvas的管理器:封装画布、上下文、粒子集合和动画逻辑 class CanvasManager { constructor(canvasElement) { this.canvas = canvasElement; this.context = canvas.getContext('2d'); // 设置Canvas尺寸(可根据需求调整) this.canvas.width = 200; this.canvas.height = 200; this.nbDots = 20; this.weight = 60; this.net = []; this.initParticles(); this.startAnimation(); } initParticles() { for (let i = 0; i < this.nbDots; i++) { const dot = new Dot(this.context); dot.setup(); this.net.push(dot); } } drawDotLines() { const copyNet = this.net.slice(); while (copyNet.length > 0) { const dot = copyNet.pop(); for (let n = 0; n < copyNet.length; n++) { const ndot = copyNet[n]; const rx = Math.pow(dot.x - ndot.x, 2); const ry = Math.pow(dot.y - ndot.y, 2); const dist = Math.sqrt(rx + ry); if (dist < this.weight) { this.context.beginPath(); this.context.strokeStyle = dot.getFillStyle(); this.context.lineWidth = 2 * (this.weight - dist) / this.weight; this.context.moveTo(dot.x, dot.y); this.context.lineTo(ndot.x, ndot.y); this.context.stroke(); } } } } update() { const ctx = this.context; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); for (const dot of this.net) { dot.update(); dot.draw(); // 粒子出界重置 if (dot.x < 0 || dot.x > this.canvas.width || dot.y < 0 || dot.y > this.canvas.height) { dot.setup(); } } this.drawDotLines(); } startAnimation() { // 用requestAnimationFrame代替setInterval,动画更流畅 const animate = () => { this.update(); requestAnimationFrame(animate); }; animate(); } } // 初始化所有Canvas const canvasElements = document.querySelectorAll('canvas[id^="dot_"]'); const managers = []; canvasElements.forEach(canvas => { managers.push(new CanvasManager(canvas)); }); // 颜色切换按钮事件(修复类名,同步所有Canvas的颜色) document.querySelectorAll(".color_change").forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); // 阻止默认跳转 index = index >= colorSets.first.length - 1 ? 0 : index + 1; localStorage.setItem('index', index); // 不需要手动调用update,动画循环会自动更新颜色 }); });
关键改进点说明
- 实例隔离:用
CanvasManager类封装每个Canvas的所有状态(上下文、粒子、动画),每个Canvas完全独立,不会互相干扰 - 粒子关联画布:Dot类现在接收对应的Canvas上下文作为参数,所有计算都基于当前画布的尺寸,不再依赖全局变量
- 更流畅的动画:用
requestAnimationFrame代替setInterval,浏览器会自动优化动画帧率,避免卡顿 - 支持任意数量Canvas:只要你的Canvas ID以
dot_开头,初始化代码会自动识别并创建管理器,加多少个都没问题 - 修复按钮事件:修正了类名拼写错误,用
querySelectorAll绑定所有按钮的点击事件,阻止默认跳转行为
内容的提问来源于stack exchange,提问作者LightninWolf
相关产品推荐
相关产品推荐

