如何创建可修改自身原始调用逻辑的对象 解决Canvas按钮重绘堆叠问题
问题根源
Canvas属于立即渲染模式的位图画布,所有绘制操作都是直接修改画布像素,不存在「绘制后可自动更新的DOM元素对象」,你每次调用show()方法都是在现有像素基础上叠加新内容,自然会出现堆叠残影。
解决方案
核心逻辑是:事件回调只更新状态属性,统一用全局渲染循环负责清屏+重绘所有元素,具体修改点如下:
- 移除所有零散调用的
show()方法,新增全局渲染循环,每次循环先清空整个画布再按最新状态重绘所有元素 - 给按钮添加hover状态标记,避免mousemove事件连续触发时重复修改属性
- 修复原代码中文字宽度计算的冗余逻辑,不需要额外绘制离屏文字
修改后的完整代码
JavaScript代码
let canvas = document.getElementById("JScanvas"), c = canvas.getContext("2d"); let mousePosition = { x: 0, y: 0 }; function buildRect(fillColor, outlineColor, outlineSize, x, y, w, h) { if (fillColor && outlineColor) { c.beginPath(); c.rect(x, y, w, h); c.fillStyle = fillColor; c.fill(); c.lineWidth = outlineSize; c.strokeStyle = outlineColor; c.stroke(); } else if (fillColor && !outlineColor) { c.beginPath(); c.rect(x, y, w, h); c.fillStyle = fillColor; c.fill(); } else if (!fillColor && outlineColor) { c.beginPath(); c.rect(x, y, w, h); c.lineWidth = outlineSize; c.strokeStyle = outlineColor; c.stroke(); } } function write(str, x, y, color, txtSize, font) { let size = txtSize.toString(); c.font = size + "px" + " " + font; c.fillStyle = color; c.fillText(str, x, y); } class button { constructor(name, order, btnColor, x, y, w, h, txtColor, txtSize, m, f) { this.name = name; this.order = order; this.btnColor = btnColor; this.x = x; this.y = y; this.w = w; this.h = h; this.txtColor = txtColor; this.txtSize = txtSize; this.m = m; this.f = f; // 新增hover状态标记 this.isHover = false; this.m.canvs.addEventListener("mousemove", (event) => { const isInRect = this.x < this.m.mPosition.x && this.m.mPosition.x < this.x + this.w && this.y < this.m.mPosition.y && this.m.mPosition.y < this.y + this.h; // 只有hover状态切换时才修改属性 if (isInRect && !this.isHover) { this.isHover = true; this.btnColor[0] = this.btnColor[0] === "grey" ? "red" : "grey"; this.x += 50; } else if (!isInRect && this.isHover) { this.isHover = false; } }); } show() { if (!this.btnColor[0] && !this.btnColor[1]) { buildRect("transparent", false, 1, this.x, this.y, this.w, this.h); } else if (!this.btnColor[0]) { buildRect(false, this.btnColor[1], 1, this.x, this.y, this.w, this.h); } else if (!this.btnColor[1]) { buildRect(this.btnColor[0], false, 1, this.x, this.y, this.w, this.h); } else { buildRect(this.btnColor[0], this.btnColor[1], 1, this.x, this.y, this.w, this.h); } c.fillStyle = this.txtColor; let theString = String(this.txtSize) + "px Arial"; c.font = theString; let width = Math.round(c.measureText(this.name).width); if (width > this.w) { let newSize = this.w / width * this.txtSize; c.font = newSize + "px Arial"; c.textAlign = "center"; c.textBaseline = "middle"; c.fillText(this.name, this.x + (this.w / 2), this.y + (this.h / 2)); } else { c.textAlign = "center"; c.textBaseline = "middle"; c.fillText(this.name, this.x + (this.w / 2), this.y + (this.h / 2)); } } clickButton(mouseX, mouseY) { return mouseX >= this.x && mouseX <= this.x + this.w && mouseY >= this.y && mouseY <= this.y + this.h; } runf() { this.f(); } } function getMousePos(canvas, evt) { const rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; } canvas.addEventListener("mousemove", (evt) => { let r = getMousePos(canvas, evt); mousePosition.x = r.x, mousePosition.y = r.y; }); let mouse = { canvs: canvas, mClicked: false, mPosition: mousePosition }; let cookie = new button("cookie", 1, ["red", false], 50, 100, 150, 50, "black", 30, mouse, 2); // 全局元素列表,后续新增按钮直接加进这个数组即可 const renderElements = [cookie]; // 全局渲染循环 function render() { // 清空画布所有内容 c.clearRect(0, 0, canvas.width, canvas.height); // 重绘所有元素 renderElements.forEach(el => el.show()); requestAnimationFrame(render); } // 启动渲染 render();
HTML代码(无修改)
<!DOCTYPE html> <html> <head> <title>Playground</title> </head> <body> <canvas width="1500" height="1500" id="JScanvas"></canvas> </body> </html>
内容的提问来源于stack exchange,提问作者robo
相关产品推荐
相关产品推荐

