You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同一页面无法重复调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 18:39:21