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

如何创建可修改自身原始调用逻辑的对象 解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 14:24:02