# UI 元素
本节将会讲解 UI 系统中常用的渲染元素以及基础使用。
## 通用属性
UI 元素包含很多通用属性,我们先来介绍这些属性,它们可以用在任何渲染元素和 UI 组件中。
### 定位属性
元素包含若干定位属性,其中最常用的是 `loc` 属性,我们也推荐全部使用这个属性来修改元素定位。其类型声明如下:
```ts
type ElementLocator = [
x?: number,
y?: number,
w?: number,
h?: number,
ax?: number,
ay?: number
];
```
这些属性两两组成一组(`x, y` 一组,`w, h` 一组,`ax, ay` 一组),每组可选填,也就是说 `x` 和 `y` 要么都填,要么都不填,以此类推。
- `x` `y`: 元素的位置,描述了在没有旋转时元素的锚点位置,例如 `[32, 32]` 就表示这个元素锚点在 `32, 32` 的位置,默认锚点在元素左上角,也就表示元素左上角在 `32, 32`。
- `w` `h`: 元素的长宽,描述了在没有缩放时元素的矩形长宽,默认是没有放缩的。
- `ax` `ay`: 元素的锚点位置,描述了元素参考点的位置,所有位置变换等将以此点作为参考点。0 表示元素最左侧或最上侧,1 表示最右侧或最下侧,可以填不在 0-1 范围内的值,例如 `[-1, 1]` 表示锚点横坐标在元素左侧一个元素宽度的位置,纵坐标在元素下边缘的位置。

示例如下:
```tsx
// 元素相对于 32, 32 位置居中(锚点在元素正中间),宽高为 64
```
除了 `loc` 属性之外,还可以通过设置 `anc` 属性来修改锚点位置,示例如下:
```tsx
// 设置锚点,效果为靠右对齐,上下居中对齐
```
你还可以手动指定 `x` `y` `width` `height` `anchorX` `anchorY` 属性,但是这种方式比较啰嗦,并不建议使用:
```tsx
```
最后说明一下元素的 `type` 属性,此属性描述了元素的定位模式,默认为 `static` 常规定位,此定位模式下元素位置会按照上述内容更改,而在 `absolute` 模式下,不论怎么修改定位属性,它都会保持在左上角的位置,可能会在一些特殊场景下使用(极度不建议使用此属性,很可能在 2.B.1 版本就会将其删除)。
### 纵深属性
可以通过 `zIndex` 属性来调整一个元素的纵深。纵深描述了元素之间的重叠关系,纵深高的会处在纵深低的元素上方,同时也会阻碍交互事件向纵深低的元素传播。必要的时候,需要通过设置纵深属性来调整层级关系。未设置时,后面的元素会处在前面的元素之上。
```tsx
// 这个元素会处在上层
// 这个元素会处在下层
```
### 效果属性
效果属性包含 `filter` `composite` 及 `alpha` 三个属性。
`filter` 表示此元素的滤镜,参考 [CanvasRenderingContext2D.filter](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter),可以填写内置函数或 svg 滤镜。默认不包含任何滤镜。示例如下:
```tsx
// 亮度变为 150%,对比度变为 120%
```
`composite` 属性描述了当前元素与在此之前渲染的元素之间的混合模式,参考 [CanvasRenderingContext2D.globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation),可以填写 26 个值。默认使用简单 `alpha` 混合,即 `source-over`。例如:
```tsx
// 使用加算方式叠加,两个颜色的 RGB 值分别相加得到最终结果
```
`alpha` 属性描述了此元素的不透明度,1 表示完全不透明,0 表示完全透明。在叠加时,所有颜色都会乘以此不透明度后叠加。默认值是完全不透明,即 1。但需要注意的是,虽然 1 表示完全不透明,但是如果画布内容本身包含透明内容(例如一个半透明矩形),即使是 1 也会表现为透明,因为叠加时会乘以 1,不透明度不变。示例如下:
```tsx
// 一个半透明元素
```
### 缓存属性
可以通过 `cache` 和 `nocache` 属性来指定这个元素的缓存行为,其中 `nocache` 表示禁用此元素的缓存机制,优先级最高,设置后必然不使用缓存。`cache` 表示启用此元素的缓存行为,常用于一些默认不启用缓存的元素,优先级低于 `cache`。这两个元素都不能动态设置,也就是说不能使用响应式来修改其值。示例如下:
```tsx
// 内部渲染内容比较简单,不需要启用缓存
// 路径较为复杂,因此启用 g-path 的缓存行为
```
### 元素溢出行为
溢出行为是指,当其子元素超出父元素的大小时,执行的行为。例如,假如父元素大小为 `200 * 200`,里面有一个子元素,大小为 `100 * 100`,位于 `(150, 50)` 的位置,这时候子元素的一部分就会超出父元素的范围。
在本渲染系统中,所有元素的默认溢出行为是裁剪,即不会显示任何溢出内容,注意调整容器的宽高。在 `nocache` 模式下,由于不受到缓存的约束,溢出内容依然会显示,不过不建议利用此特性来编写 UI,因为这种行为可能会在后续的更新中修改。
### 隐藏元素
可以使用 `hidden` 属性来隐藏元素:
```tsx
const hidden = ref(false);
// 一般使用一个响应式变量来控制隐藏行为,因为设置成常量没有任何意义
;
```
### 交互属性
交互属性包括 `cursor` 和 `noevent`。前者描述了鼠标覆盖在当前元素上时的指针样式,参考 [CSS: cursor](https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor)。示例如下:
```tsx
// 鼠标放置在该元素上时使用小手样式
```
`noevent` 表明当前元素将不会触发任何事件,事件将会下穿至纵深更低的元素。示例如下:
```tsx
// 设置为 noevent 模式
// 这样的话这个 onClick 就可以正常触发了
```
### 高清与抗锯齿
包含 `hd` `anti` `noanti` 三个属性,`hd` 表示是否启用高清,大部分元素是默认启用的,除了几个像素风为主的元素(地图渲染等);`anti` 表示手动启用画布的抗锯齿行为,一般用于默认不启用抗锯齿的元素;`noanti` 表示手动关闭元素的抗锯齿行为,优先级高于 `anti`,一般用于像素风图片展示、图标显示等,同时也有助于提高渲染性能。
```tsx
// 关闭高清
// 关闭抗锯齿
// 启用地图渲染的抗锯齿
```
### 元素变换属性
可以通过调整 `transform` 属性来修改元素的线性变换,包括平移、旋转、缩放。如果是简易的变换,可以使用 `rotate` `scale` 属性来修改旋转、缩放,使用 `loc` 来修改位置:
```tsx
// 旋转 90 度,横向放缩为 1.5 倍,纵向不放缩
```
我们没有设置锚点属性,那么需要注意旋转后,`loc` 属性所标明的位置将不再是左上角的那个点,因为旋转后,原本在左上角的点将会变成右上角的点。旋转时,顺时针为正,逆时针为负。
使用上面这种方式时,没有办法指定变换的顺序。一般情况下,变换的顺序将会影响结果,例如先旋转,再放缩,和先放缩,再旋转,其结果是不同的。下面我们来讲解一下图形学中的 2D 矩阵变换的基本概念,以及如何使用 `transform` 属性。
### Transform 矩阵变换
大部分情况下用不到此属性,此属性理解难度较大,如果不是必须使用此属性,可以不看此小节。以下矩阵变换内容由 `DeepSeek R1` 模型生成,并稍作修改。
#### 为什么需要变换矩阵?
在 2D 图形学中,变换矩阵(3x3)可以统一表示以下基本变换操作:
- 平移(Translation)
- 旋转(Rotation)
- 缩放(Scale)
- 错切(Skew)
通过矩阵乘法可以将多个变换组合为单个矩阵运算,其通用数学表示为(列主序):
$Transform=\begin{bmatrix} a & b & 0 \\ c & d & 0 \\ e & f & 1 \end{bmatrix}$
其中:
- `a,d` 控制缩放和旋转
- `b,c` 控制错切
- `e,f` 控制平移
#### 变换组合原理
矩阵乘法具有结合性但不具有交换性,变换顺序会影响最终效果,矩阵按从右到左的顺序应用变换:
最终矩阵 = 平移矩阵 × 旋转矩阵 × 缩放矩阵 × 原始坐标
#### Transform 类核心功能
首先创建其实例:
```ts
import { Transform } from '@motajs/render';
const trans = new Transform();
```
之后可以链式调用来修改矩阵:
```ts
// 链式调用示例
trans
.setTranslate(100, 50)
.rotate(Math.PI / 4)
.scale(2, 1.5);
```
方法对比
| 方法类型 | 特点 | 函数 |
| -------- | -------------------------- | ---------------------------------------------------- |
| 叠加变换 | 在现有变换基础上叠加新变换 | `translate` `rotate` `scale` `transform` |
| 直接设置 | 覆盖当前变换参数 | `setTranslate` `setRotate` `setScale` `setTransform` |
#### 关键方法详解
平移变换:
```ts
// 相对移动(叠加)
trans.translate(20, -10);
// 绝对定位(覆盖)
trans.setTranslate(200, 150);
```
旋转变换:
```ts
// 叠加旋转 45 度
trans.rotate(Math.PI / 4);
// 设置绝对旋转角度
trans.setRotate(Math.PI / 2);
```
缩放变换:
```ts
// X 轴放大 2 倍,Y 轴不变(叠加)
trans.scale(2, 1);
// 设置绝对缩放比例
trans.setScale(0.5, 0.8);
```
#### 高级功能
矩阵操作:
```ts
// 手动设置变换矩阵
trans.setTransform(
1,
0, // 缩放部分
0,
1, // 旋转部分
100,
50 // 平移部分
);
// 矩阵相乘(组合变换)
const combined = trans.multiply(otherTransform);
```
坐标变换:
```ts
// 将局部坐标转换为世界坐标,即计算一个坐标经过此变换矩阵计算后的位置
const worldPos = trans.transformed(10, 20);
// 将世界坐标转换回局部坐标,即计算一个坐标经过此变换矩阵逆转换后的位置
const localPos = trans.untransformed(150, 80);
```
#### 性能优化技巧
使用自动更新机制:
```ts
import { ITransformUpdatable } from '@motajs/render';
// 绑定可更新对象
class MyElement implements ITransformUpdatable {
updateTransform() {
console.log('变换已更新!');
}
}
const element = new MyElement();
trans.bind(element); // 变换修改时自动触发 updateTransform
```
#### 最佳实践
推荐变换执行顺序:
1. 缩放(Scale)
2. 旋转(Rotate)
3. 平移(Translate)
```ts
// 正确顺序示例
trans
.setScale(2)
.rotate(Math.PI / 3)
.setTranslate(100, 50);
```
组合复杂变换:
```ts
// 创建子变换
const childTrans = trans
.clone() // 从 trans 复制一个相同的变换出来,以防止修改原变换
.rotate(-Math.PI / 6)
.translate(30, 0);
// 应用组合变换
const finalTrans = trans.multiply(childTrans);
```
#### 应用到元素
赋值给 `transform` 属性即可
```tsx
```
#### 常见问题排查
1. 变换不生效?
- 验证绑定的对象是否实现 `updateTransform`
- 检查有没有把 `trans` 对象赋值给元素的 `transform` 属性
2. 性能问题
- 避免高频调用 `setTransform`
- 优先使用叠加方法代替矩阵直接操作
- 利用 `clone()` 复用已有变换
## `sprite` 标签
`sprite` 标签是一个允许你自定义渲染内容的标签,它新增了一个属性 `render` 属性,允许你传入一个函数来执行自定义渲染。函数定义如下:
```ts
type RenderFn = (canvas: MotaOffscreenCanvas2D, transform: Transform) => void;
```
- `canvas`: 要渲染至的画布,一般直接将内容渲染至这个画布上
- `transform`: 当前元素的变换矩阵,相对于父元素,不常用
多数情况下,我们只会使用到第一个参数,`MotaOffscreenCanvas2D` 接口请参考 [API 文档](../api/motajs-render-core)。下面是一个典型案例:
```tsx
const render = (canvas: MotaOffscreenCanvas2D) => {
const { ctx, width, height } = canvas; // 获取画布上下文以及长宽
ctx.fillStyle = '#d84'; // 设置填充样式
ctx.fillRect(0, 0, width, height); // 绘制一个矩形
};
```
`sprite` 元素的应用场景并不算多,因为样板内置的各种元素已经足够丰富,此元素一般只会在一些特殊情况下或性能敏感情况下使用。
## `container` 标签
`container` 表示一个容器,它可以将一系列元素作为子元素并渲染。它并没有新增任何属性。如果你想渲染子元素,请务必使用此元素包裹,除此之外的大部分元素是不能渲染子元素的。
## `container-custom` 标签
`container-custom` 是另一种容器,它允许你自定义对子元素的渲染方案,对于特殊场景下有一定的作用,例如样板自带的 `Scroll` 组件就使用此标签实现。它新增了一个 `render` 参数,定义如下:
```ts
type CustomContainerRenderFn = (
canvas: MotaOffscreenCanvas2D,
children: RenderItem[],
transform: Transform
) => void;
```
- `canvas`: 要渲染至的画布,一般直接将内容渲染至这个画布上
- `children`: 要渲染的子元素,按 `zIndex` 升序排列
- `transform`: 当前元素的变换矩阵,相对于父元素,不常用
典型案例如下:
```tsx
const render = (
canvas: MotaOffscreenCanvas2D,
children: RenderItem[],
transform: Transform
) => {
// 顺序遍历子元素,保证纵深关系正确
children.forEach(v => {
if (v.hidden) return; // 如果元素隐藏,则不渲染
// 调用子元素的渲染函数,传入 canvas 和 transform 作为参数
v.renderContent(canvas, transform);
});
};
;
```
## `text` 标签
`text` 标签用于显示文字,它会自动计算文字的宽高并设置为元素宽高,因此不要手动指定宽高,否则可能会引起位置错误。它新增了这些属性:
```ts
interface TextProps extends BaseProps {
/** 要渲染的文字 */
text?: string;
/** 文字的填充样式 */
fillStyle?: CanvasStyle;
/** 文字的描边样式 */
strokeStyle?: CanvasStyle;
/** 文字的字体 */
font?: Font;
/** 文字的描边粗细 */
strokeWidth?: number;
}
```
典型案例如下:
```tsx
import { Font } from '@motajs/render';
;
```
## `image` 标签
`image` 标签允许你显示一张图片,包含一个 `image` 属性,传入图片对象(注意不是注册图片名称)。用例如下:
```tsx
// 获取注册的图片
const img = core.material.images.images['myImage.png'];
// 显示图片
;
```
## `icon` 标签
`icon` 标签用于显示一个图标,可以包含动画帧。它有如下参数:
```ts
export interface IconProps extends BaseProps {
/** 图标 id 或数字 */
icon: AllNumbers | AllIds;
/** 显示图标的第几帧 */
frame?: number;
/** 是否开启动画,开启后 frame 参数无效 */
animate?: boolean;
}
```
使用案例如下:
```tsx
// 显示绿史莱姆,开启动画
```
## `winskin` 标签
`winskin` 标签允许你显示一个 rpg maker 风格的背景图(window skin),它有如下参数:
```ts
export interface WinskinProps extends BaseProps {
/** winskin 的图片 id */
image: ImageIds;
/** 边框大小 */
borderSize?: number;
}
```
其中边框大小默认为 32,表示上边框和下边框加起来共 32 像素,即四周边框 16 像素宽。用例如下:
```tsx
// 使用 winskin.png 作为图片,四周边框宽度为 24 像素
```
## 图形标签
本小节讲解图形相关的标签,以下内容由 `DeepSeek R1` 模型生成并稍作修改。
### 通用属性说明
所有图形元素均支持以下核心属性:
| 属性分类 | 关键参数 | 说明 |
| -------------- | ------------------------- | -------------------------------------------------------- |
| **填充与描边** | `fill` `stroke` | 控制是否填充/描边(不同元素默认值不同) |
| **样式控制** | `fillStyle` `strokeStyle` | 填充和描边样式,支持颜色/渐变等(如 `'#f00'`) |
| **线型设置** | `lineWidth` `lineDash` | 线宽、虚线模式(如 `[5, 3]` 表示 5 像素实线+3 像素间隙) |
| **高级控制** | `fillRule` `actionStroke` | 填充规则(非零/奇偶)、是否仅在描边区域响应交互 |
### 矩形 ``
矩形的定位直接使用 `loc` 即可,示例如下:
```tsx
// 基础矩形,矩形默认仅填充模式,因此如果需要描边的话需要手动指定 fill 和 stroke 参数
// 注意如果仅指定 stroke 参数的话,会变为仅描边形式
```
### 圆形和扇形 ``
参数如下:
```ts
interface CirclesProps {
radius: number; // 半径
start?: number; // 起始弧度(默认0)
end?: number; // 结束弧度(默认2π)
/**
* 圆属性参数,可以填 `[圆心 x 坐标,圆心 y 坐标,半径,起始角度,终止角度]`,是 x, y, radius, start, end 的简写,
* 其中半径可选,后两项要么都填,要么都不填
*/
circle?: CircleParams;
}
```
示例如下:
```tsx
// 完整圆形
// 扇形(60度到180度)
```
### 直线 ``
核心参数:
```ts
interface LineProps {
x1: number; // 起点X
y1: number; // 起点Y
x2: number; // 终点X
y2: number; // 终点Y
/** 直线属性简写参数,可以填 `[x1, y1, x2, y2]`,都是必填 */
line: [number, number, number, number];
}
```
示例如下:
```tsx
// 普通直线
// 带箭头的参考线
```
### 三次贝塞尔曲线 ``
核心参数:
```ts
interface BezierProps {
sx: number; // 起点X
sy: number; // 起点Y
cp1x: number; // 控制点1X
cp1y: number; // 控制点1Y
cp2x: number; // 控制点2X(三次贝塞尔)
cp2y: number; // 控制点2Y
ex: number; // 终点X
ey: number; // 终点Y
/** 三次贝塞尔曲线参数简写,可以填 `[sx, sy, cp1x, cp1y, cp2x, cp2y, ex, ey]`,都是必填 */
curve: BezierParams;
}
```
示例如下:
```tsx
// 三次贝塞尔曲线
;
// 动态路径
const path = computed(() => [
startX.value,
startY.value,
control1X.value,
control1Y.value,
control2X.value,
control2Y.value,
endX.value,
endY.value
]);
;
```
### 二次贝塞尔曲线 ``
核心参数:
```ts
interface BezierProps {
sx: number; // 起点X
sy: number; // 起点Y
cpx: number; // 控制点X
cpy: number; // 控制点Y
ex: number; // 终点X
ey: number; // 终点Y
/** 二次贝塞尔曲线参数,可以填 `[sx, sy, cpx, cpy, ex, ey]`,都是必填 */
curve: QuadParams;
}
```
示例如下:
```tsx
// 二次贝塞尔曲线
;
// 动态路径
const path = computed(() => [
startX.value,
startY.value,
controlX.value,
controlY.value,
endX.value,
endY.value
]);
;
```
### 圆角矩形 ``
圆角矩形的核心参数与 CSS 的 border-radius 类似,如下:
```ts
interface RectRProps extends GraphicPropsBase {
/**
* 圆形圆角参数,可以填 `[r1, r2, r3, r4]`,后三项可选。填写不同数量下的表现:
* - 1个:每个角都是 `r1` 半径的圆
* - 2个:左上和右下是 `r1` 半径的圆,右上和左下是 `r2` 半径的圆
* - 3个:左上是 `r1` 半径的圆,右上和左下是 `r2` 半径的圆,右下是 `r3` 半径的圆
* - 4个:左上、右上、左下、右下 分别是 `r1, r2, r3, r4` 半径的圆
*/
circle?: RectRCircleParams;
/**
* 椭圆圆角参数,可以填 `[rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4]`,
* 两两一组,后三组可选,填写不同数量下的表现:
* - 1组:每个角都是 `[rx1, ry1]` 半径的椭圆
* - 2组:左上和右下是 `[rx1, ry1]` 半径的椭圆,右上和左下是 `[rx2, ry2]` 半径的椭圆
* - 3组:左上是 `[rx1, ry1]` 半径的椭圆,右上和左下是 `[rx2, ey2]` 半径的椭圆,右下是 `[rx3, ry3]` 半径的椭圆
* - 4组:左上、右上、左下、右下 分别是 `[rx1, ry1], [rx2, ry2], [rx3, ry3], [rx4, ry4]` 半径的椭圆
*/
ellipse?: RectREllipseParams;
}
```
示例如下:
```tsx
// 四角圆角半径都为 10 的圆角矩形
// 每个角都是横向半径为 10,纵向半径为 5 的椭圆
// 左上和右下是半径为 10 的圆角,左下和右上是半径为 25 的圆角
```
### 自定义路径 ``
核心参数:
```ts
interface PathProps {
path?: Path2D; // 自定义路径对象
}
```
示例:
```tsx
// 创建五角星
const starPath = new Path2D();
// ...路径绘制逻辑
;
```
### 最佳实践建议
1. 交互增强:
```tsx
```
2. 样式复用:
```tsx
// 创建样式对象
const themeStyle = {
fillStyle: '#2c3e50',
strokeStyle: '#ecf0f1',
lineWidth: 2
};
```