介绍
tui-entine 是 tui-plus-vapor 内部使用的一套自研 Canvas Flex 布局渲染引擎,目录位于:
uni_modules/tui-entine
它的主要作用不是替代普通页面开发,而是为一些"原生组件难以直接表达"或"更适合纯 Canvas 绘制"的组件提供底层能力。
核心特点:
- 🎨 Canvas 渲染:基于 Canvas 2D 的高性能渲染
- 📐 Flex 布局:完整的 Flexbox 布局系统
- 🎯 事件系统:支持触摸事件和点击事件
- ⚡ 局部更新:支持单个元素的局部重绘
- 🔧 样式丰富:支持完整的 CSS 样式属性
适用场景:
- 颜色面板、拖拽选区、透明度滑条这类高频重绘组件
- 摇杆、手势控制器、方向盘一类强交互组件
- 侧滑菜单、局部虚拟绘制、复杂滚动菜单一类需要自己控制渲染范围的组件
- 纯 Canvas 的复杂布局与交互组件
不适合的场景:
- 普通表单页
- 普通静态布局页
- 直接用原生组件就能很好解决的问题
快速开始
基本接入方式
在组件里使用 tui-entine,通常会结合 t-canvas-engine 一起使用。
典型流程是:
- 模板中放一个
t-canvas-engine - 在
initFinished中拿到引擎实例或 Canvas 上下文 - 使用
TuiEngine创建节点 - 组装元素树
- 调用
draw(...)完成首次绘制 - 在交互中调用
updateElement(...)或重新绘制
一个典型模板入口长这样:
<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
创建一个视图元素(容器节点)。
createView() : ITuiView返回值: ITuiView - 视图元素实例
示例:
const view = engine.createView();
view.style.width = 100;
view.style.height = 100;
view.style.backgroundColor = "#f0f0f0";createText
创建一个文本元素。
createText() : ITuiText返回值: ITuiText - 文本元素实例
属性:
value: string- 文本内容values: string[]- 多行文本内容
示例:
const text = engine.createText();
text.value = "Hello World";
text.style.fontSize = 16;
text.style.color = "#333333";
text.style.textAlign = "center";createImage
创建一个图片元素。
createImage() : ITuiImage返回值: ITuiImage - 图片元素实例
属性:
src: string- 图片路径mode: string- 图片模式(默认:'scaleToFill')onload: (event: TuiImageLoadEvent) => void- 加载完成回调onerror: (event: TuiImageErrorEvent) => void- 加载错误回调
示例:
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
创建一个按钮元素。
createButton() : ITuiButton返回值: ITuiButton - 按钮元素实例
属性:
value: string- 按钮文本disabled: boolean- 是否禁用loading: boolean- 是否加载中
示例:
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。
draw(e : ITuiElement, css : UTSJSONObject | null) : void参数:
e: ITuiElement- 根元素css: UTSJSONObject | null- CSS 样式配置(可选)
示例:
// 首次绘制
engine.draw(root, null);
// 带样式配置绘制
const styles = {
".container": {
backgroundColor: "#ffffff",
},
};
engine.draw(root, styles);updateElement
更新单个元素的绘制结果(局部重绘)。
updateElement(e : ITuiElement, clear : boolean | null) : void参数:
e: ITuiElement- 要更新的元素clear: boolean | null- 是否清除之前的绘制(可选)
示例:
// 更新元素位置
element.style.transform.translateX = 50;
engine.updateElement(element, true);
// 更新元素颜色
element.style.backgroundColor = "#ff0000";
engine.updateElement(element, false);measureText
测量文本的宽度。
measureText(text : string, fontSize : number) : number参数:
text: string- 要测量的文本fontSize: number- 字体大小
返回值: number - 文本宽度
示例:
const width = engine.measureText("Hello World", 16);
console.log("文本宽度:", width);getElementById
通过 ID 获取元素。
getElementById(id : string) : ITuiElement | null参数:
id: string- 元素 ID
返回值: ITuiElement | null - 找到的元素,未找到返回 null
示例:
const element = engine.getElementById("my-element");
if (element != null) {
element.style.backgroundColor = "#ff0000";
}handleTouchEvent
处理触摸事件。
handleTouchEvent(e : TuiTouchEvent) : void参数:
e: TuiTouchEvent- 触摸事件对象
示例:
function touchstart(e: TuiTouchEvent) {
engine.handleTouchEvent(e);
}
function touchmove(e: TuiTouchEvent) {
engine.handleTouchEvent(e);
}
function touchend(e: TuiTouchEvent) {
engine.handleTouchEvent(e);
}元素 API
appendChild
添加子元素到当前元素。
appendChild(...spreadChild : ITuiElement[]) : void参数:
...spreadChild: ITuiElement[]- 要添加的子元素数组
示例:
const parent = engine.createView();
const child1 = engine.createView();
const child2 = engine.createView();
parent.appendChild(child1, child2);removeChild
从当前元素中移除子元素。
removeChild(child : ITuiElement) : ITuiElement参数:
child: ITuiElement- 要移除的子元素
返回值: ITuiElement - 被移除的元素
示例:
const removed = parent.removeChild(child);
console.log("已移除元素:", removed.id);addEventListener
为元素添加事件监听器。
addEventListener(type : string, listener : (event : ITuiTouchEvent) => void) : number参数:
type: string- 事件类型('touchstart', 'touchmove', 'touchend', 'click')listener: (event : ITuiTouchEvent) => void- 事件处理函数
返回值: number - 事件监听器 ID
示例:
const listenerId = element.addEventListener("click", (event) => {
console.log("元素被点击", event.clientX, event.clientY);
});removeEventListener
移除元素的事件监听器。
removeEventListener(listener : number) : void参数:
listener: number- 事件监听器 ID
示例:
element.removeEventListener(listenerId);getBoundingClientRect
获取元素的位置和尺寸信息。
getBoundingClientRect() : TuiDOMRect返回值: TuiDOMRect - 元素位置信息
示例:
const rect = element.getBoundingClientRect();
console.log("元素位置:", rect.x, rect.y);
console.log("元素尺寸:", rect.width, rect.height);样式 API
尺寸样式
interface ITuiStyle {
// 尺寸
width?: number;
height?: number;
minWidth?: number;
minHeight?: number;
maxWidth?: number;
maxHeight?: number;
}示例:
element.style.width = 100;
element.style.height = 50;
element.style.minWidth = 80;
element.style.maxWidth = 200;定位样式
interface ITuiStyle {
// 定位
left?: number;
right?: number;
top?: number;
bottom?: number;
position?: string; // 'absolute' | 'relative'
}示例:
element.style.position = "absolute";
element.style.left = 10;
element.style.top = 20;Flex 布局
interface ITuiStyle {
// Flex 布局
flex?: number;
flexDirection?: string;
flexWrap?: string;
justifyContent?: string;
alignItems?: string;
alignSelf?: string;
alignContent?: string;
}示例:
// 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";文本样式
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;
}示例:
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;边框样式
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;
}示例:
element.style.borderWidth = 1;
element.style.borderColor = "#e0e0e0";
element.style.borderRadius = 8;变换样式
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;
}示例:
element.style.transform = {
translateX: 50,
translateY: 100,
scaleX: 1.5,
rotate: Math.PI / 4,
};
element.style.opacity = 0.8;事件 API
touchstart
触摸开始事件。
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
触摸移动事件。
element.addEventListener("touchmove", (event: ITuiTouchEvent) => {
console.log("触摸移动", event.clientX, event.clientY);
});touchend
触摸结束事件。
element.addEventListener("touchend", (event: ITuiTouchEvent) => {
console.log("触摸结束", event.clientX, event.clientY);
});click
点击事件(短时间内的触摸开始和结束)。
element.addEventListener("click", (event: ITuiTouchEvent) => {
console.log("元素被点击", event.clientX, event.clientY);
});注意: 点击事件是引擎自动生成的,当触摸持续时间 < 300ms 且移动距离 < 10px 时触发。
使用示例
基础布局示例
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);
}动态更新示例
// 更新元素位置
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);
}复杂布局示例
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 渲染解决方案
让复杂交互组件的开发变得简单而高效! 🚀