Vue 3 自定义渲染器实战:用 createRenderer 把组件画到 Canvas

test12026-10-181 次阅读

自定义渲染器解决什么问题

Vue 默认把模板渲染成 DOM。但在一些特殊场景(Canvas、终端 UI、原生 canvas 图表、游戏)里根本没有真实 DOM 节点。Vue 3 把"渲染到什么目标"抽象成了渲染器接口,只要提供一组节点操作函数,就能让同一套组件语法渲染到任意画布。核心 API 是 createRenderer。

实现一个最小 Canvas 渲染器

下面把每个"元素"当成画布上的一个矩形,text 当成文字。我们只实现 createElement、insert、patchProp、remove、createText、setText 几个钩子即可跑起来。

// canvasRenderer.ts
import { createRenderer } from 'vue';

interface CanvasNode {
  type: string;
  props: Record<string, any>;
  children: CanvasNode[];
  parent: CanvasNode | null;
}

function draw(ctx: CanvasRenderingContext2D, node: CanvasNode, x: number, y: number) {
  if (node.type === 'text') {
    ctx.fillStyle = '#111';
    ctx.fillText(node.props.text ?? '', x, y + 16);
    return;
  }
  const w = node.props.width ?? 120;
  const h = node.props.height ?? 40;
  ctx.strokeStyle = '#3b82f6';
  ctx.strokeRect(x, y, w, h);
  let cy = y + 8;
  for (const child of node.children) {
    draw(ctx, child, x + 8, cy);
    cy += 20;
  }
}

export function createCanvasApp(rootComponent: any, canvas: HTMLCanvasElement) {
  const ctx = canvas.getContext('2d')!;
  const root: CanvasNode = { type: 'root', props: {}, children: [], parent: null };

  const renderer = createRenderer<CanvasNode, CanvasNode>({
    createElement: (type) => ({ type, props: {}, children: [], parent: null }),
    createText: (text) => ({ type: 'text', props: { text }, children: [], parent: null }),
    insert: (child, parent) => { child.parent = parent; parent.children.push(child); },
    remove: (child) => {
      const p = child.parent;
      if (p) p.children = p.children.filter((c) => c !== child);
    },
    patchProp: (el, key, _old, value) => { el.props[key] = value; },
    setText: (el, text) => { el.props.text = text; },
    parentNode: (n) => n.parent!,
    nextSibling: () => null as any,
    setElementText: () => {},
  });

  const app = renderer.createApp(rootComponent);
  app.mount(root);
  // 挂载后重绘整棵节点树
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  draw(ctx, root, 10, 10);
  return app;
}

用组件语法描述画布内容

// App.vue
<template>
  <box :width="200" :height="80">
    <text>标题</text>
    <text>副标题</text>
  </box>
</template>
<script setup>
import Box from './Box.vue';
import Text from './Text.vue';
</script>

落地经验

  • 自定义渲染器不是用来替代 DOM 的,它只适合"目标介质没有 DOM"的场景。
  • 真实项目建议在 patchProp/insert 里节流重绘,避免每次状态变更都整树重画。
  • react-three-fiber、Vue 的 native 渲染都是同一思路:把声明式组件映射到非 DOM 节点。
T

test1

文章作者

自定义渲染器解决什么问题 Vue 默认把模板渲染成 DOM。但在一些特殊场景(Canvas、终端 UI、原生 canv...

分类
技术
发布时间
2026-10-18
字数
约 2473 字
阅读
1 次