同一页面无法重复调用JS Canvas动效函数的问题求助
解决同一页面多次调用Canvas交互动效的问题
你的核心问题是原代码用了全局单例的Nodes对象,所有状态、事件绑定、动画循环都依赖这个唯一对象,所以第二次调用时会覆盖第一个实例的配置,导致只有最后一个生效。要实现多实例,需要把单例对象改成可复用的构造函数,让每个Canvas对应独立的实例。
关键修改点
- 将
Nodes对象重构为构造函数CanvasNodes,通过new创建独立实例 - 把所有硬编码的
Nodes替换为this,确保每个实例的上下文独立 - 让
init方法接收canvas的ID参数,不再固定绑定单一ID - 修复事件回调和动画循环里的上下文指向问题,避免全局污染
- 调整resize事件处理,每个实例独立监听,不再覆盖全局
window.onresize
修改后的完整代码
JS代码
// 保留requestAnimationFrame的兼容代码(这部分不用改) (function () { var lastTime = 0; var vendors = ["ms", "moz", "webkit", "o"]; for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] || window[vendors[x] + "CancelRequestAnimationFrame"]; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function () { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { clearTimeout(id); }; })(); // 重构为构造函数,支持多实例 function CanvasNodes(canvasId) { // Settings this.density = 20; this.reactionSensitivity = 10; this.points = []; this.lines = [[], [], [], [], []]; this.mouse = { x: 10, y: 10, down: false }; this.animation = null; this.canvasId = canvasId; this.canvas = null; this.context = null; // 绑定事件回调的上下文,避免this指向错误 this.mouseMove = this.mouseMove.bind(this); this.mouseDown = this.mouseDown.bind(this); this.mouseUp = this.mouseUp.bind(this); this.mouseOut = this.mouseOut.bind(this); this.handleResize = this.handleResize.bind(this); } CanvasNodes.prototype.init = function () { // 根据传入的ID获取canvas元素 this.canvas = document.getElementById(this.canvasId); this.context = this.canvas.getContext("2d"); this.context.lineJoin = "bevel"; this.canvas.width = window.innerHeight * 2; this.canvas.height = window.innerHeight; this.canvas.style.display = "block"; // 绑定事件到当前实例的canvas this.canvas.addEventListener("mousemove", this.mouseMove, false); this.canvas.addEventListener("mousedown", this.mouseDown, false); this.canvas.addEventListener("mouseup", this.mouseUp, false); this.canvas.addEventListener("mouseout", this.mouseOut, false); // 独立监听resize事件,不覆盖全局回调 window.addEventListener("resize", this.handleResize, false); this.preparePoints(); this.draw(); }; CanvasNodes.prototype.preparePoints = function () { this.points = []; this.lines = [[], [], [], [], []]; var center = window.innerHeight / 2; for (var i = -1000; i < this.canvas.width + 1000; i += this.density) { var line1 = { y: center - 17, originalY: center - -100, color: "#249FA4" }; var line2 = { y: center - 17, originalY: center - -50, color: "#3DB8B5" }; var line3 = { y: center - 17, originalY: center - 0, color: "#F8DCAA" }; var line4 = { y: center - 17, originalY: center - 50, color: "#F1B30A" }; var line5 = { y: center - 17, originalY: center - 100, color: "#E77419" }; line1["x"] = i; line1["originalX"] = i; line2["x"] = i; line2["originalX"] = i; line3["x"] = i; line3["originalX"] = i; line4["x"] = i; line4["originalX"] = i; line5["x"] = i; line5["originalX"] = i; this.points.push(line1); this.points.push(line2); this.points.push(line3); this.points.push(line4); this.points.push(line5); this.lines[0].push(line1); this.lines[1].push(line2); this.lines[2].push(line3); this.lines[3].push(line4); this.lines[4].push(line5); } }; CanvasNodes.prototype.updatePoints = function () { for (var i = 0; i < this.points.length; i++) { var currentPoint = this.points[i]; var theta = Math.atan2( currentPoint.y - this.mouse.y, currentPoint.x - this.mouse.x ); var distance = this.mouse.down ? (this.reactionSensitivity * 300) / Math.sqrt( (this.mouse.x - currentPoint.x) ** 2 + (this.mouse.y - currentPoint.y) ** 2 ) : (this.reactionSensitivity * 150) / Math.sqrt( (this.mouse.x - currentPoint.x) ** 2 + (this.mouse.y - currentPoint.y) ** 2 ); currentPoint.x += Math.cos(theta) * distance + (currentPoint.originalX - currentPoint.x) * 0.07; currentPoint.y += Math.sin(theta) * distance + (currentPoint.originalY - currentPoint.y) * 0.07; } }; CanvasNodes.prototype.drawPoints = function () { for (var i = 0; i < 5; i++) { var line = this.lines[i]; this.context.beginPath(); this.context.lineJoin = "round"; this.context.moveTo(line[0].x, line[0].y); this.context.strokeStyle = line[0].color; this.context.lineWidth = 50; for (var j = 1; j < line.length - 2; j++) { var point = line[j]; var xc = (point.x + line[j + 1].x) / 2; var yc = (point.y + line[j + 1].y) / 2; this.context.quadraticCurveTo(point.x, point.y, xc, yc); } this.context.stroke(); this.context.closePath(); } }; CanvasNodes.prototype.draw = function () { // 动画循环指向当前实例的draw方法 this.animation = requestAnimationFrame(() => this.draw()); this.clear(); this.updatePoints(); this.drawPoints(); }; CanvasNodes.prototype.clear = function () { this.canvas.width = this.canvas.width; }; CanvasNodes.prototype.mouseDown = function (event) { this.mouse.down = true; }; CanvasNodes.prototype.mouseUp = function (event) { this.mouse.down = false; }; CanvasNodes.prototype.mouseMove = function (event) { // 修正坐标获取:pageX/pageY是相对于文档,clientX/clientY是相对于视口,根据需求调整 this.mouse.x = event.clientX; this.mouse.y = event.clientY; }; CanvasNodes.prototype.mouseOut = function (event) { this.mouse.x = -1000; this.mouse.y = -1000; this.mouse.down = false; }; CanvasNodes.prototype.handleResize = function () { cancelAnimationFrame(this.animation); this.canvas.width = window.innerHeight * 2; this.canvas.height = Math.max(window.innerHeight, 1000); this.preparePoints(); this.draw(); }; // 初始化多个实例 setTimeout(function () { new CanvasNodes("stripes1").init(); new CanvasNodes("stripes2").init(); }, 10);
HTML代码
<!-- 第一个Canvas区块 --> <div style="width: 100%; height: 50vh; border: 1px solid #eee;"> <canvas id="stripes1" class="container"></canvas> </div> <!-- 第二个Canvas区块 --> <div style="width: 100%; height: 50vh; border: 1px solid #eee;"> <canvas id="stripes2" class="container"></canvas> </div>
说明
- 每个
CanvasNodes实例都有独立的状态、事件监听和动画循环,不会互相干扰 - 你可以根据需要创建任意多个实例,只要给每个Canvas分配唯一的ID即可
- 修正了原代码中
mouseMove里pageY重复赋值的错误(原代码里x和y都用了pageY,应该是pageX和pageY或者clientX/clientY)
内容的提问来源于stack exchange,提问作者ALiE
相关产品推荐
相关产品推荐

