Skip to content

Commit 480c8bb

Browse files
author
anewzhuang
committed
feat: 增加 readme 相关注释,增加 backgroundImageType
1 parent 451214a commit 480c8bb

File tree

9 files changed

+405
-240
lines changed

9 files changed

+405
-240
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#### 2025.7.14
2+
1. `A` 支持图片九宫格拉伸、平铺渲染模式
3+
14
#### 2024.11.11
25
1. `A` 新增insertElement;
36
2. `U` 优化ts声明协议;

docs/components/image.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
| 属性 | 类型 | 是否必填 | 说明 |
77
|----------------|--------|--------|------------------------|
88
| src| string ||图片链接, 修改图片的src属性会自动请求新的图片并渲染|
9-
| type| string || 图片的类型,simple默认,sliced九宫格,tiled平铺渲染|
10-
|inset|string|| 九宫格设置的区域,left\|top\|right\|bottom,如'10 20 30 10'|
119

1210
::: tip
1311
1.小游戏开放数据域场景图片路径不需要加./作为前缀,以小游戏根目录作为根目录;

docs/components/overview.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,28 @@ Layout 通过 xml 组织布局,Layout 支持的标签列表如下。
112112
|---------------|--------|--------|--------------------------|
113113
| backgroundColor | string | | 背景的颜色,支持 6 位 16 进制、8 位 16 进制、rgb、rgba 四种格式的颜色 |
114114
| backgroundImage | string | | 背景图,格式为 'url(https:/www.foo.com/xxx.png)' |
115+
| backgroundImageType | string | simple | 背景图片的渲染类型,详见下方"图像渲染模式"说明 |
116+
| backgroundImageInset | string | | 背景图片九宫格设置的区域,格式为 'left top right bottom',如'10 20 30 10' |
115117
| opacity | number | 1 | 透明度,范围[0, 1],0表示透明,1表示不透明 |
116118
| transform | string | | transform 属性允许你旋转和缩放给定元素,目前支持的格式 `rotate(360deg)` |
117119

120+
### 图像
121+
122+
支持的标签:`image`
123+
| 属性 | 类型 | 默认值 | 说明 |
124+
|---------------|--------|--------|--------------------------|
125+
| imageType | string | simple | 图像的渲染类型,详见下方"图像渲染模式"说明 |
126+
| imageInset | string | | 图像九宫格设置的区域,格式为 'left top right bottom',如'15 15 15 15' |
127+
128+
#### 图像渲染模式
129+
130+
> v1.0.29 支持三种图像渲染模式,这些模式既适用于 Image 组件(通过 `imageType` 属性),也适用于所有支持背景图片的容器(通过 `backgroundImageType` 属性)。
131+
132+
| 渲染模式 | 描述 | 适用场景 | 参数要求 | 性能 |
133+
|---------|------|----------|----------|------|
134+
| **simple** | 图像被拉伸以适应容器尺寸 | 大多数普通场景、图标、头像等 ||
135+
| **sliced** | 图像分为9个区域,角部保持原始尺寸,边部单方向拉伸,中心双方向拉伸 | 按钮、面板、对话框等UI元素 | 需要设置 `*Inset` 参数 |
136+
| **tiled** | 图像以原始尺寸重复平铺,自动处理边界裁剪 | 背景纹理、图案、瓷砖效果等 ||
118137

119138
#### transform 特殊说明
120139
v1.0.5版本开始支持 transform,目前为止,transform 不会递归影响子节点,也就是父节点旋转缩放了之后子节点不会连带旋转缩放,要实现连带旋转缩放的能力,一方面是目前为止需求不够强,另一方面,对代码体积影响较大,会违背 Layout 轻量的初衷,暂时不做改造。

docs/overview/plugin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const Layout = requirePlugin('Layout').default;
8686
## 版本列表
8787
| 版本 | 特性 |
8888
| --------------- | ------------------- |
89+
| 1.0.16 | 新增图片九宫格拉伸、平铺渲染模式 |
8990
| 1.0.15 | 新增文本换行相关特性:whiteSpace、wordBreak |
9091
| 1.0.14 | 新增 classList 特性 |
9192
| 1.0.13 | 新增insertElement,优化ts声明协议|

src/common/imageRenderer.ts

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { IInsetParams } from '../components/styleParser';
2+
3+
export type ImageRenderMode = 'simple' | 'sliced' | 'tiled';
4+
5+
export interface IImageRenderOptions {
6+
img: HTMLImageElement;
7+
x: number;
8+
y: number;
9+
width: number;
10+
height: number;
11+
mode: ImageRenderMode;
12+
inset?: IInsetParams;
13+
}
14+
15+
/**
16+
* 图像渲染器 - 统一处理图像渲染逻辑
17+
*/
18+
export class ImageRenderer {
19+
/**
20+
* 渲染图像
21+
*/
22+
static render(ctx: CanvasRenderingContext2D, options: IImageRenderOptions): void {
23+
const { img, x, y, width, height, mode, inset } = options;
24+
25+
switch (mode) {
26+
case 'simple':
27+
ImageRenderer.renderSimple(ctx, img, x, y, width, height);
28+
break;
29+
case 'sliced':
30+
ImageRenderer.renderSliced(ctx, img, x, y, width, height, inset);
31+
break;
32+
case 'tiled':
33+
ImageRenderer.renderTiled(ctx, img, x, y, width, height);
34+
break;
35+
}
36+
}
37+
38+
/**
39+
* 简单拉伸渲染
40+
*/
41+
private static renderSimple(
42+
ctx: CanvasRenderingContext2D,
43+
img: HTMLImageElement,
44+
x: number,
45+
y: number,
46+
width: number,
47+
height: number
48+
): void {
49+
ctx.drawImage(img, x, y, width, height);
50+
}
51+
52+
/**
53+
* 九宫格渲染
54+
*/
55+
private static renderSliced(
56+
ctx: CanvasRenderingContext2D,
57+
img: HTMLImageElement,
58+
x: number,
59+
y: number,
60+
width: number,
61+
height: number,
62+
inset?: IInsetParams
63+
): void {
64+
if (!inset) {
65+
console.warn('[Layout] sliced render need inset parameters');
66+
ImageRenderer.renderSimple(ctx, img, x, y, width, height);
67+
return;
68+
}
69+
70+
const { left, top, right, bottom } = inset;
71+
const imgWidth = img.width;
72+
const imgHeight = img.height;
73+
74+
// 计算源区域尺寸
75+
const centerSrcWidth = imgWidth - left - right;
76+
const centerSrcHeight = imgHeight - top - bottom;
77+
78+
// 计算目标区域尺寸
79+
const targetCenterWidth = Math.max(0, width - left - right);
80+
const targetCenterHeight = Math.max(0, height - top - bottom);
81+
82+
// 1. 渲染四个角(保持原样)
83+
ImageRenderer.renderCorners(ctx, img, x, y, width, height, imgWidth, imgHeight, left, top, right, bottom);
84+
85+
// 2. 渲染四条边(拉伸)
86+
ImageRenderer.renderEdges(ctx, img, x, y, width, height, imgWidth, imgHeight, left, top, right, bottom, centerSrcWidth, centerSrcHeight, targetCenterWidth, targetCenterHeight);
87+
88+
// 3. 渲染中心区域(拉伸)
89+
if (targetCenterWidth > 0 && targetCenterHeight > 0 && centerSrcWidth > 0 && centerSrcHeight > 0) {
90+
ctx.drawImage(img, left, top, centerSrcWidth, centerSrcHeight,
91+
x + left, y + top, targetCenterWidth, targetCenterHeight);
92+
}
93+
}
94+
95+
/**
96+
* 平铺渲染
97+
*/
98+
private static renderTiled(
99+
ctx: CanvasRenderingContext2D,
100+
img: HTMLImageElement,
101+
x: number,
102+
y: number,
103+
width: number,
104+
height: number
105+
): void {
106+
const pattern = ctx.createPattern(img, 'repeat');
107+
if (!pattern) return;
108+
109+
ctx.save();
110+
111+
// 设置裁剪区域
112+
ctx.beginPath();
113+
ctx.rect(x, y, width, height);
114+
ctx.clip();
115+
116+
// 设置pattern并填充
117+
ctx.fillStyle = pattern;
118+
ctx.fillRect(x, y, width, height);
119+
120+
ctx.restore();
121+
}
122+
123+
/**
124+
* 渲染九宫格的四个角
125+
*/
126+
private static renderCorners(
127+
ctx: CanvasRenderingContext2D,
128+
img: HTMLImageElement,
129+
x: number,
130+
y: number,
131+
width: number,
132+
height: number,
133+
imgWidth: number,
134+
imgHeight: number,
135+
left: number,
136+
top: number,
137+
right: number,
138+
bottom: number
139+
): void {
140+
// 左上角
141+
if (left > 0 && top > 0) {
142+
ctx.drawImage(img, 0, 0, left, top, x, y, left, top);
143+
}
144+
145+
// 右上角
146+
if (right > 0 && top > 0) {
147+
ctx.drawImage(img, imgWidth - right, 0, right, top,
148+
x + width - right, y, right, top);
149+
}
150+
151+
// 左下角
152+
if (left > 0 && bottom > 0) {
153+
ctx.drawImage(img, 0, imgHeight - bottom, left, bottom,
154+
x, y + height - bottom, left, bottom);
155+
}
156+
157+
// 右下角
158+
if (right > 0 && bottom > 0) {
159+
ctx.drawImage(img, imgWidth - right, imgHeight - bottom, right, bottom,
160+
x + width - right, y + height - bottom, right, bottom);
161+
}
162+
}
163+
164+
/**
165+
* 渲染九宫格的四条边
166+
*/
167+
private static renderEdges(
168+
ctx: CanvasRenderingContext2D,
169+
img: HTMLImageElement,
170+
x: number,
171+
y: number,
172+
width: number,
173+
height: number,
174+
imgWidth: number,
175+
imgHeight: number,
176+
left: number,
177+
top: number,
178+
right: number,
179+
bottom: number,
180+
centerSrcWidth: number,
181+
centerSrcHeight: number,
182+
targetCenterWidth: number,
183+
targetCenterHeight: number
184+
): void {
185+
// 上边 - 水平拉伸
186+
if (top > 0 && targetCenterWidth > 0) {
187+
ctx.drawImage(img, left, 0, centerSrcWidth, top,
188+
x + left, y, targetCenterWidth, top);
189+
}
190+
191+
// 下边 - 水平拉伸
192+
if (bottom > 0 && targetCenterWidth > 0) {
193+
ctx.drawImage(img, left, imgHeight - bottom, centerSrcWidth, bottom,
194+
x + left, y + height - bottom, targetCenterWidth, bottom);
195+
}
196+
197+
// 左边 - 垂直拉伸
198+
if (left > 0 && targetCenterHeight > 0) {
199+
ctx.drawImage(img, 0, top, left, centerSrcHeight,
200+
x, y + top, left, targetCenterHeight);
201+
}
202+
203+
// 右边 - 垂直拉伸
204+
if (right > 0 && targetCenterHeight > 0) {
205+
ctx.drawImage(img, imgWidth - right, top, right, centerSrcHeight,
206+
x + width - right, y + top, right, targetCenterHeight);
207+
}
208+
}
209+
}

src/components/elements.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import imageManager from '../common/imageManager';
55
import TinyEmitter from 'tiny-emitter';
66
import { IDataset, Callback } from '../types/index'
77
import { IElementOptions } from './types';
8-
import { backgroundImageParser, parseTransform, IRenderForLayout } from './styleParser';
8+
import { backgroundImageParser, parseTransform, parseInsetParams, validateImageType, IRenderForLayout } from './styleParser';
9+
import { ImageRenderer, ImageRenderMode } from '../common/imageRenderer';
910
import { convertPercent } from '../common/util'
1011
import env from '../env';
1112

@@ -355,6 +356,22 @@ export default class Element {
355356
}
356357
break;
357358

359+
case 'backgroundImageType':
360+
this.renderForLayout.backgroundImageType = validateImageType(val);
361+
break;
362+
363+
case 'backgroundImageInset':
364+
this.renderForLayout.backgroundImageInset = parseInsetParams(val) || undefined;
365+
break;
366+
367+
case 'imageType':
368+
this.renderForLayout.imageType = validateImageType(val);
369+
break;
370+
371+
case 'imageInset':
372+
this.renderForLayout.imageInset = parseInsetParams(val) || undefined;
373+
break;
374+
358375
case 'transform':
359376
delete this.renderForLayout.scaleX;
360377
delete this.renderForLayout.scaleY;
@@ -368,6 +385,22 @@ export default class Element {
368385
this.renderForLayout.backgroundImage = undefined;
369386
break;
370387

388+
case 'backgroundImageType':
389+
this.renderForLayout.backgroundImageType = undefined;
390+
break;
391+
392+
case 'backgroundImageInset':
393+
this.renderForLayout.backgroundImageInset = undefined;
394+
break;
395+
396+
case 'imageType':
397+
this.renderForLayout.imageType = undefined;
398+
break;
399+
400+
case 'imageInset':
401+
this.renderForLayout.imageInset = undefined;
402+
break;
403+
371404
case 'transform':
372405
delete this.renderForLayout.scaleX;
373406
delete this.renderForLayout.scaleY;
@@ -764,9 +797,28 @@ export default class Element {
764797
}
765798

766799
if (this.renderForLayout.backgroundImage) {
767-
ctx.drawImage(this.renderForLayout.backgroundImage, drawX - originX, drawY - originY, box.width, box.height);
800+
this.renderBackgroundImage(ctx, drawX, drawY, originX, originY, box.width, box.height);
768801
}
769802

770803
return { needStroke, needClip, originX, originY, drawX, drawY, width, height };
771804
}
805+
806+
/**
807+
* 渲染背景图片,支持三种模式:simple、sliced、tiled
808+
*/
809+
private renderBackgroundImage(ctx: CanvasRenderingContext2D, drawX: number, drawY: number, originX: number, originY: number, width: number, height: number) {
810+
const img = this.renderForLayout.backgroundImage!;
811+
const mode = (this.renderForLayout.backgroundImageType || 'simple') as ImageRenderMode;
812+
const inset = this.renderForLayout.backgroundImageInset;
813+
814+
ImageRenderer.render(ctx, {
815+
img,
816+
x: drawX - originX,
817+
y: drawY - originY,
818+
width,
819+
height,
820+
mode,
821+
inset
822+
});
823+
}
772824
}

0 commit comments

Comments
 (0)