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
相关产品推荐
相关产品推荐

