Skip to content
高性能 Canvas 渲染引擎

tui-entine

自研 Canvas Flex 布局渲染引擎,为复杂交互组件提供高性能绘制与局部更新能力。

渲染方式
Canvas 2D
布局系统
Flexbox
更新策略
局部重绘

介绍

tui-entinetui-plus-vapor 内部使用的一套自研 Canvas Flex 布局渲染引擎,目录位于:

  • uni_modules/tui-entine

它的主要作用不是替代普通页面开发,而是为一些"原生组件难以直接表达"或"更适合纯 Canvas 绘制"的组件提供底层能力。

核心特点:

  • 🎨 Canvas 渲染:基于 Canvas 2D 的高性能渲染
  • 📐 Flex 布局:完整的 Flexbox 布局系统
  • 🎯 事件系统:支持触摸事件和点击事件
  • 局部更新:支持单个元素的局部重绘
  • 🔧 样式丰富:支持完整的 CSS 样式属性

适用场景:

  • 颜色面板、拖拽选区、透明度滑条这类高频重绘组件
  • 摇杆、手势控制器、方向盘一类强交互组件
  • 侧滑菜单、局部虚拟绘制、复杂滚动菜单一类需要自己控制渲染范围的组件
  • 纯 Canvas 的复杂布局与交互组件

不适合的场景:

  • 普通表单页
  • 普通静态布局页
  • 直接用原生组件就能很好解决的问题

快速开始

基本接入方式

在组件里使用 tui-entine,通常会结合 t-canvas-engine 一起使用。

典型流程是:

  1. 模板中放一个 t-canvas-engine
  2. initFinished 中拿到引擎实例或 Canvas 上下文
  3. 使用 TuiEngine 创建节点
  4. 组装元素树
  5. 调用 draw(...) 完成首次绘制
  6. 在交互中调用 updateElement(...) 或重新绘制

一个典型模板入口长这样:

vue
<template>
  <t-canvas-engine
    @initFinished="initFinished"
    @touchstart="touchstart"
    @touchmove="touchmove"
    @touchend="touchend"
  ></t-canvas-engine>
</template>

<script setup lang="uts">
import { TuiEngine } from "@/uni_modules/tui-entine";
import { ITuiEngine, ITuiElement } from "@/uni_modules/tui-entine/types";

let eng: ITuiEngine | null = null;

function initFinished(engine: ITuiEngine) {
  eng = engine;

  // 创建根容器
  const root = engine.createView();
  root.style.width = engine.width;
  root.style.height = engine.height;
  root.style.backgroundColor = "#ffffff";
  root.style.justifyContent = "center";
  root.style.alignItems = "center";

  // 创建文本元素
  const title = engine.createText();
  title.value = "Hello tui-entine";
  title.style.fontSize = 16;
  title.style.color = "#333333";

  // 组装元素树
  root.appendChild(title);

  // 绘制
  engine.draw(root, null);
}
</script>

引擎 API

createView

创建一个视图元素(容器节点)。

ts
createView() : ITuiView

返回值: ITuiView - 视图元素实例

示例:

ts
const view = engine.createView();
view.style.width = 100;
view.style.height = 100;
view.style.backgroundColor = "#f0f0f0";

createText

创建一个文本元素。

ts
createText() : ITuiText

返回值: ITuiText - 文本元素实例

属性:

  • value: string - 文本内容
  • values: string[] - 多行文本内容

示例:

ts
const text = engine.createText();
text.value = "Hello World";
text.style.fontSize = 16;
text.style.color = "#333333";
text.style.textAlign = "center";

createImage

创建一个图片元素。

ts
createImage() : ITuiImage

返回值: ITuiImage - 图片元素实例

属性:

  • src: string - 图片路径
  • mode: string - 图片模式(默认:'scaleToFill')
  • onload: (event: TuiImageLoadEvent) => void - 加载完成回调
  • onerror: (event: TuiImageErrorEvent) => void - 加载错误回调

示例:

ts
const image = engine.createImage();
image.src = "/static/logo.png";
image.style.width = 100;
image.style.height = 100;
image.onload = (event) => {
  console.log("图片加载完成", event.detail.width, event.detail.height);
};

createButton

创建一个按钮元素。

ts
createButton() : ITuiButton

返回值: ITuiButton - 按钮元素实例

属性:

  • value: string - 按钮文本
  • disabled: boolean - 是否禁用
  • loading: boolean - 是否加载中

示例:

ts
const button = engine.createButton();
button.value = "点击我";
button.style.width = 120;
button.style.height = 40;
button.style.backgroundColor = "#2979ff";
button.style.color = "#ffffff";
button.style.borderRadius = 8;

draw

绘制元素树到 Canvas。

ts
draw(e : ITuiElement, css : UTSJSONObject | null) : void

参数:

  • e: ITuiElement - 根元素
  • css: UTSJSONObject | null - CSS 样式配置(可选)

示例:

ts
// 首次绘制
engine.draw(root, null);

// 带样式配置绘制
const styles = {
  ".container": {
    backgroundColor: "#ffffff",
  },
};
engine.draw(root, styles);

updateElement

更新单个元素的绘制结果(局部重绘)。

ts
updateElement(e : ITuiElement, clear : boolean | null) : void

参数:

  • e: ITuiElement - 要更新的元素
  • clear: boolean | null - 是否清除之前的绘制(可选)

示例:

ts
// 更新元素位置
element.style.transform.translateX = 50;
engine.updateElement(element, true);

// 更新元素颜色
element.style.backgroundColor = "#ff0000";
engine.updateElement(element, false);

measureText

测量文本的宽度。

ts
measureText(text : string, fontSize : number) : number

参数:

  • text: string - 要测量的文本
  • fontSize: number - 字体大小

返回值: number - 文本宽度

示例:

ts
const width = engine.measureText("Hello World", 16);
console.log("文本宽度:", width);

getElementById

通过 ID 获取元素。

ts
getElementById(id : string) : ITuiElement | null

参数:

  • id: string - 元素 ID

返回值: ITuiElement | null - 找到的元素,未找到返回 null

示例:

ts
const element = engine.getElementById("my-element");
if (element != null) {
  element.style.backgroundColor = "#ff0000";
}

handleTouchEvent

处理触摸事件。

ts
handleTouchEvent(e : TuiTouchEvent) : void

参数:

  • e: TuiTouchEvent - 触摸事件对象

示例:

ts
function touchstart(e: TuiTouchEvent) {
  engine.handleTouchEvent(e);
}

function touchmove(e: TuiTouchEvent) {
  engine.handleTouchEvent(e);
}

function touchend(e: TuiTouchEvent) {
  engine.handleTouchEvent(e);
}

元素 API

appendChild

添加子元素到当前元素。

ts
appendChild(...spreadChild : ITuiElement[]) : void

参数:

  • ...spreadChild: ITuiElement[] - 要添加的子元素数组

示例:

ts
const parent = engine.createView();
const child1 = engine.createView();
const child2 = engine.createView();

parent.appendChild(child1, child2);

removeChild

从当前元素中移除子元素。

ts
removeChild(child : ITuiElement) : ITuiElement

参数:

  • child: ITuiElement - 要移除的子元素

返回值: ITuiElement - 被移除的元素

示例:

ts
const removed = parent.removeChild(child);
console.log("已移除元素:", removed.id);

addEventListener

为元素添加事件监听器。

ts
addEventListener(type : string, listener : (event : ITuiTouchEvent) => void) : number

参数:

  • type: string - 事件类型('touchstart', 'touchmove', 'touchend', 'click')
  • listener: (event : ITuiTouchEvent) => void - 事件处理函数

返回值: number - 事件监听器 ID

示例:

ts
const listenerId = element.addEventListener("click", (event) => {
  console.log("元素被点击", event.clientX, event.clientY);
});

removeEventListener

移除元素的事件监听器。

ts
removeEventListener(listener : number) : void

参数:

  • listener: number - 事件监听器 ID

示例:

ts
element.removeEventListener(listenerId);

getBoundingClientRect

获取元素的位置和尺寸信息。

ts
getBoundingClientRect() : TuiDOMRect

返回值: TuiDOMRect - 元素位置信息

示例:

ts
const rect = element.getBoundingClientRect();
console.log("元素位置:", rect.x, rect.y);
console.log("元素尺寸:", rect.width, rect.height);

样式 API

尺寸样式

ts
interface ITuiStyle {
  // 尺寸
  width?: number;
  height?: number;
  minWidth?: number;
  minHeight?: number;
  maxWidth?: number;
  maxHeight?: number;
}

示例:

ts
element.style.width = 100;
element.style.height = 50;
element.style.minWidth = 80;
element.style.maxWidth = 200;

定位样式

ts
interface ITuiStyle {
  // 定位
  left?: number;
  right?: number;
  top?: number;
  bottom?: number;
  position?: string; // 'absolute' | 'relative'
}

示例:

ts
element.style.position = "absolute";
element.style.left = 10;
element.style.top = 20;

Flex 布局

ts
interface ITuiStyle {
  // Flex 布局
  flex?: number;
  flexDirection?: string;
  flexWrap?: string;
  justifyContent?: string;
  alignItems?: string;
  alignSelf?: string;
  alignContent?: string;
}

示例:

ts
// Flex 容器
container.style.display = "flex";
container.style.flexDirection = "row";
container.style.justifyContent = "center";
container.style.alignItems = "center";

// Flex 子元素
child.style.flex = 1;
child.style.alignSelf = "center";

文本样式

ts
interface ITuiStyle {
  // 文本样式
  fontSize?: number;
  lineHeight?: number;
  lines?: number;
  textAlign?: string;
  verticalAlign?: string;
  color?: string;
  fontWeight?: string;
  fontFamily?: string;
  letterSpacing?: number;
  textOverflow?: string;
  whiteSpace?: string;
  wordBreak?: string;
}

示例:

ts
text.style.fontSize = 16;
text.style.lineHeight = 1.5;
text.style.color = "#333333";
text.style.textAlign = "center";
text.style.textOverflow = "ellipsis";
text.style.lines = 2;

边框样式

ts
interface ITuiStyle {
  // 边框
  borderWidth?: number;
  borderLeftWidth?: number;
  borderRightWidth?: number;
  borderTopWidth?: number;
  borderBottomWidth?: number;
  borderColor?: string;
  borderTopColor?: string;
  borderBottomColor?: string;
  borderLeftColor?: string;
  borderRightColor?: string;
  borderRadius?: number;
  borderTopLeftRadius?: number;
  borderTopRightRadius?: number;
  borderBottomLeftRadius?: number;
  borderBottomRightRadius?: number;
}

示例:

ts
element.style.borderWidth = 1;
element.style.borderColor = "#e0e0e0";
element.style.borderRadius = 8;

变换样式

ts
interface ITuiTransform {
  translateX?: number;
  translateY?: number;
  scaleX?: number;
  scaleY?: number;
  rotate?: number;
  skewX?: number;
  skewY?: number;
}

interface ITuiStyle {
  transform?: ITuiTransform;
  opacity?: number;
  rotateX?: number;
  rotateY?: number;
}

示例:

ts
element.style.transform = {
  translateX: 50,
  translateY: 100,
  scaleX: 1.5,
  rotate: Math.PI / 4,
};
element.style.opacity = 0.8;

事件 API

touchstart

触摸开始事件。

ts
element.addEventListener("touchstart", (event: ITuiTouchEvent) => {
  console.log("触摸开始", event.clientX, event.clientY);
});

事件属性:

  • type: string - 事件类型
  • target: ITuiElement - 触发事件的元素
  • currentTarget: ITuiElement - 当前处理事件的元素
  • clientX: number - 客户端 X 坐标
  • clientY: number - 客户端 Y 坐标
  • pageX: number - 页面 X 坐标
  • pageY: number - 页面 Y 坐标
  • offsetX: number - 元素内 X 偏移
  • offsetY: number - 元素内 Y 偏移
  • stopPropagation(): void - 停止事件冒泡
  • preventDefault(): void - 阻止默认行为

touchmove

触摸移动事件。

ts
element.addEventListener("touchmove", (event: ITuiTouchEvent) => {
  console.log("触摸移动", event.clientX, event.clientY);
});

touchend

触摸结束事件。

ts
element.addEventListener("touchend", (event: ITuiTouchEvent) => {
  console.log("触摸结束", event.clientX, event.clientY);
});

click

点击事件(短时间内的触摸开始和结束)。

ts
element.addEventListener("click", (event: ITuiTouchEvent) => {
  console.log("元素被点击", event.clientX, event.clientY);
});

注意: 点击事件是引擎自动生成的,当触摸持续时间 < 300ms 且移动距离 < 10px 时触发。

使用示例

基础布局示例

ts
import { TuiEngine } from "@/uni_modules/tui-entine";
import { ITuiEngine } from "@/uni_modules/tui-entine/types";

let eng: ITuiEngine | null = null;

function initFinished(engine: ITuiEngine) {
  eng = engine;

  // 创建根容器
  const root = engine.createView();
  root.style.width = engine.width;
  root.style.height = engine.height;
  root.style.backgroundColor = "#ffffff";
  root.style.padding = 20;

  // 创建标题
  const title = engine.createText();
  title.value = "欢迎使用 tui-entine";
  title.style.fontSize = 24;
  title.style.color = "#333333";
  title.style.marginBottom = 20;

  // 创建内容容器
  const content = engine.createView();
  content.style.flexDirection = "column";
  content.style.flex = 1;

  // 创建按钮
  const button = engine.createButton();
  button.value = "点击我";
  button.style.width = 200;
  button.style.height = 44;
  button.style.backgroundColor = "#2979ff";
  button.style.color = "#ffffff";
  button.style.borderRadius = 8;
  button.style.marginTop = 20;

  // 添加点击事件
  button.addEventListener("click", (event) => {
    console.log("按钮被点击");
  });

  // 组装元素树
  content.appendChild(title, button);
  root.appendChild(content);

  // 绘制
  engine.draw(root, null);
}

动态更新示例

ts
// 更新元素位置
function moveElement(element: ITuiElement, x: number, y: number) {
  element.style.transform.translateX = x;
  element.style.transform.translateY = y;
  engine.updateElement(element, true);
}

// 更新元素颜色
function changeColor(element: ITuiElement, color: string) {
  element.style.backgroundColor = color;
  engine.updateElement(element, false);
}

复杂布局示例

ts
function createComplexLayout(engine: ITuiEngine) {
  const root = engine.createView();
  root.style.width = engine.width;
  root.style.height = engine.height;
  root.style.backgroundColor = "#f5f5f5";

  // 创建头部
  const header = engine.createView();
  header.style.height = 60;
  header.style.backgroundColor = "#2979ff";
  header.style.justifyContent = "center";
  header.style.alignItems = "center";

  const headerText = engine.createText();
  headerText.value = "标题";
  headerText.style.fontSize = 18;
  headerText.style.color = "#ffffff";

  header.appendChild(headerText);

  // 创建内容区
  const content = engine.createView();
  content.style.flex = 1;
  content.style.padding = 20;

  // 创建多个卡片
  for (let i = 0; i < 3; i++) {
    const card = engine.createView();
    card.style.backgroundColor = "#ffffff";
    card.style.borderRadius = 8;
    card.style.padding = 15;
    card.style.marginBottom = 15;

    const cardTitle = engine.createText();
    cardTitle.value = `卡片 ${i + 1}`;
    cardTitle.style.fontSize = 16;
    cardTitle.style.color = "#333333";
    cardTitle.style.marginBottom = 10;

    card.appendChild(cardTitle);
    content.appendChild(card);
  }

  // 组装元素树
  root.appendChild(header, content);

  // 绘制
  engine.draw(root, null);
}

实际应用案例

在 t-color 中的应用

t-color 组件使用 tui-entine 实现了:

  • 颜色选择区域绘制
  • 透明度滑条与拖拽手柄
  • 颜色块、文本区域与面板元素
  • 用户拖拽时的局部更新

在 t-joystick 中的应用

t-joystick 组件使用 tui-entine 实现了:

  • 摇杆底盘绘制
  • 箭头图片、指南针图片、小球图片的组合排布
  • 摇杆旋转与位移更新
  • 多触点跟踪与拖动反馈

在 t-slider-menu 中的应用

t-slider-menu 组件使用 tui-entine 实现了:

  • 自定义左侧菜单渲染
  • 自定义右侧内容区渲染
  • 按当前滚动位置裁剪可见项
  • 惯性滚动、边界回弹、局部更新

相关文件

  • uni_modules/tui-entine/index.uts - 引擎主文件
  • uni_modules/tui-entine/types/index.uts - 类型定义
  • uni_modules/tui-entine/components/view.uts - 视图组件
  • uni_modules/tui-entine/components/text.uts - 文本组件
  • uni_modules/tui-entine/components/image.uts - 图片组件
  • uni_modules/tui-entine/components/button.uts - 按钮组件
  • uni_modules/tui-entine/core/layout.uts - 布局计算
  • uni_modules/tui-entine/core/render.uts - 渲染逻辑

tui-entine 为 UniApp X 提供了一套强大的 Canvas 渲染解决方案

让复杂交互组件的开发变得简单而高效! 🚀