Skip to content

自绘画布 canvas:onDraw 与绘制原语 ​

<canvas> ​

稳定 · 响应式自绘

自绘画布。在 onDraw(ctx) 里用 7 个原语直接落笔,坐标是画布局部坐标 (0,0 就是画布左上角),越界部分自动裁掉,不会溢出到界面其它地方。 在 onDraw 里读到的 signal 变化会自动触发重绘。

Prop类型说明
onDrawfunction绘制函数,收到 ctx。必须传函数本身
width / heightnumber缺省 200×120;canvas 不是容器,拿不到父容器交叉轴 stretch
background / border颜色画布盒自身的底色与描边
disabledboolean降饱和

ctx 提供的能力 ​

原语签名说明
fillRect(x, y, w, h, color)实心矩形
strokeRect(x, y, w, h, color)空心矩形(1px 边框)
fillCircle(cx, cy, r, color)实心圆
strokeCircle(cx, cy, r, color)圆环
line(x1, y1, x2, y2, color)直线
drawText(text, x, y, size, color)文本
clear(color)整块填充
width / heightnumber(只读)画布尺寸
js
const [tick, setTick] = createSignal(0);
const DATA = [4, 7, 3, 8, 5, 9, 6, 2];

const chart = h("canvas", {
  width: 246, height: 110,
  onDraw: (ctx) => {
    // 读到 tick() → 订阅它,变化时自动重绘
    const cur = tick() % DATA.length;
    ctx.fillRect(0, 0, ctx.width, ctx.height, "#fafafa");
    ctx.line(0, 96, ctx.width - 1, 96, "#cccccc");
    ctx.drawText("bars " + DATA.length, 4, 2, 12, "#555555");
    for (let i = 0; i < DATA.length; i++) {
      const bh = DATA[i] * 8;
      const x = 6 + i * 30;
      ctx.fillRect(x, 96 - bh, 24, bh, i === cur ? "#c0392b" : "#7f8c8d");
    }
  },
});

setInterval(() => setTick(tick() + 1), 500);

三个最容易踩的坑

① 传函数本身:写 onDraw={draw()} 会在挂载时先调一次,再把返回值 (undefined)当回调 —— 现象是"什么都不画"且不报错。
② 依赖靠"读":必须在 onDraw 里读 signal 才会订阅。读普通变量不会产生依赖, 数据变了画布不动。
③ 画布不铺底:与 HTML canvas 一样是透明的,要底色就自己 ctx.fillRect 或挂 background。

onDraw 每次依赖变化会执行两遍(一遍用空操作 ctx 收集依赖、一遍用真 ctx 落笔), 所以它必须是纯绘制函数 —— 别在里面改状态、别做耗时计算、也别把 signal 读取藏在 if (ctx.width > 0) 之后(依赖收集时尺寸可能还是 0,那条分支不跑就收不到订阅)。

  • 无路径 / 变换 / 渐变 / 抗锯齿开关 / 可调线宽 / drawImage。
  • 无内置帧循环:动画要自己挂 requestAnimationFrame。
  • 7 个原语都是"最后一笔覆盖"的即时绘制,没有图层与合成模式。

Apache License 2.0 · 用 ❤ 和 Go 编写