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

Canvas输入文本重复绘制问题:重复绑定input事件监听器的解决方案咨询

解决Canvas文本绘制重复绑定事件的问题

嗨,我明白你遇到的麻烦了——每次切换行高调用onTyping时,input事件的监听器会被重复绑定,导致输入时多个位置都画出文本。这是因为每次调用addEventListener都会给元素新增一个监听器,而不是替换掉旧的,所以调用几次就会有几个监听器同时工作。

下面给你两种可行的解决方案,推荐第一种,逻辑更简洁高效:


方案一:只绑定一次事件,用变量控制绘制高度

这个思路是把input事件的监听从onTyping里抽出来,只绑定一次,然后通过一个全局变量来存储当前要绘制的行高。切换行时只更新这个变量,输入时就会用最新的高度绘制文本。

修改后的完整代码:

<input class="text" onfocus="this.value=''" type="text" value=""/>
<button class="lines" onclick="SwitchLine()">
  <img src="icons/up-and-down-opposite-double-arrows-side-by-side.png" alt="切换行">
</button>
let gLineLocation = 0;
let currentCanvasHeight = 50; // 默认绘制高度
const textInput = document.querySelector(".text");

// 页面加载完成后,只绑定一次input事件
window.addEventListener('load', () => {
  textInput.addEventListener("input", (ev) => {
    const inputText = ev.target.value;
    // 关键:每次绘制前清空Canvas,避免旧文本残留
    gCtx.clearRect(0, 0, gCanvas.width, gCanvas.height);
    gCtx.font = "40px Impact";
    gCtx.strokeText(inputText, 20, currentCanvasHeight);
  });
});

function onTyping(canvasHeight = 50) {
  // 这里不再绑定事件,只更新绘制高度
  currentCanvasHeight = canvasHeight;
  // 可选:如果切换行后想立即更新当前文本的位置,手动触发一次input事件
  textInput.dispatchEvent(new Event('input'));
}

function SwitchLine() {
  gLineLocation++;
  if (gLineLocation === 1) {
    onTyping(gCanvas.height / 2);
  } else if (gLineLocation === 2) {
    onTyping(gCanvas.height - 10);
    gLineLocation = 0;
  }
}

为什么这样改?

  • 事件只绑定一次,不会出现重复执行的问题
  • 用currentCanvasHeight统一管理绘制高度,切换行时只更新这个变量,逻辑更清晰
  • 加入clearRect清空画布,解决原代码中文本叠加的问题(原代码每次输入都会在旧位置上叠加新文本)

方案二:每次绑定前移除旧监听器

如果你因为某些原因必须在onTyping里绑定事件,可以通过保存监听器的引用,每次绑定前先移除旧的监听器,再绑定新的。

修改后的代码:

<input class="text" onfocus="this.value=''" type="text" value=""/>
<button class="lines" onclick="SwitchLine()">
  <img src="icons/up-and-down-opposite-double-arrows-side-by-side.png" alt="切换行">
</button>
let gLineLocation = 0;
let inputListener = null; // 保存监听器的引用
const textInput = document.querySelector(".text");

function onTyping(canvasHeight = 50) {
  // 先移除旧的监听器(如果存在)
  if (inputListener) {
    textInput.removeEventListener("input", inputListener);
  }
  // 创建新的监听器函数,并保存引用
  inputListener = (ev) => {
    const inputText = ev.target.value;
    gCtx.clearRect(0, 0, gCanvas.width, gCanvas.height);
    gCtx.font = "40px Impact";
    gCtx.strokeText(inputText, 20, canvasHeight);
  };
  // 绑定新的监听器
  textInput.addEventListener("input", inputListener);
}

function SwitchLine() {
  gLineLocation++;
  if (gLineLocation === 1) {
    onTyping(gCanvas.height / 2);
  } else if (gLineLocation === 2) {
    onTyping(gCanvas.height - 10);
    gLineLocation = 0;
  }
}

// 初始化调用一次
window.addEventListener('load', () => {
  onTyping();
});

注意点:

  • 必须保存监听器的引用(inputListener),因为removeEventListener需要和addEventListener传入完全相同的函数引用才能移除成功
  • 同样加入了clearRect来清空画布,避免文本叠加

额外优化建议

原代码中input标签的onkeyup="onTyping()"是多余的,因为每次按键都会触发onTyping,如果用方案二的话会频繁移除和绑定监听器,影响性能。所以建议去掉这个属性,只在初始化和切换行时调用onTyping。

内容的提问来源于stack exchange,提问作者issac razabi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 18:52:28