Skip to content

Commit 1dabde9

Browse files
author
anewzhuang
committed
feat: 增加九宫格&tiled展示
1 parent f9d67ac commit 1dabde9

File tree

2 files changed

+235
-5
lines changed

2 files changed

+235
-5
lines changed

docs/components/image.md

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

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

src/components/image.ts

Lines changed: 233 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,60 @@ import Element from './elements';
22
import imageManager from '../common/imageManager';
33
import { IElementOptions } from './types';
44

5+
/**
6+
* 图片类型
7+
*/
8+
export enum ImageType {
9+
/**
10+
* 普通类型
11+
*/
12+
SIMPLE = 'simple',
13+
/**
14+
* 切片(九宫格)类型。
15+
*/
16+
SLICED = 'sliced',
17+
/**
18+
* 平铺类型
19+
*/
20+
TILED = 'tiled',
21+
}
22+
23+
/**
24+
* 九宫格配置参数
25+
*/
26+
export interface IInsetParams {
27+
/** 左边界距离 */
28+
left: number;
29+
/** 上边界距离 */
30+
top: number;
31+
/** 右边界距离 */
32+
right: number;
33+
/** 下边界距离 */
34+
bottom: number;
35+
}
36+
537
interface IImageOptions extends IElementOptions {
638
src?: string;
39+
type?: ImageType;
40+
/** 九宫格配置 */
41+
inset?: string;
742
}
843

944
export default class Image extends Element {
1045
private imgsrc: string;
1146
public type = 'Image';
1247
public img: HTMLImageElement | null;
48+
public imageType: ImageType = ImageType.SIMPLE;
49+
private insetParams?: IInsetParams;
1350

1451
constructor(opts: IImageOptions) {
1552
const {
1653
style = {},
1754
idName = '',
1855
className = '',
1956
src = '',
57+
type = ImageType.SIMPLE,
58+
inset = '0 0 0 0',
2059
dataset,
2160
} = opts;
2261

@@ -27,15 +66,31 @@ export default class Image extends Element {
2766
style,
2867
});
2968

69+
// 解析inset参数并验证
70+
if (inset) {
71+
const values = inset.split(' ').map(Number);
72+
if (values.length === 4 && values.every(v => !isNaN(v) && v >= 0)) {
73+
const [left, top, right, bottom] = values;
74+
this.insetParams = {
75+
left,
76+
top,
77+
right,
78+
bottom,
79+
};
80+
} else {
81+
console.warn('Invalid inset parameter format. Expected: "left top right bottom"');
82+
}
83+
}
84+
3085
this.imgsrc = src;
86+
this.imageType = type;
3187

3288
this.img = imageManager.loadImage(this.src, (img, fromCache) => {
3389
if (fromCache) {
3490
this.img = img;
3591
} else {
3692
if (!this.isDestroyed) {
3793
this.img = img;
38-
// 当图片加载完成,实例可能已经被销毁了
3994
this.root?.emit('repaint');
4095
}
4196
}
@@ -59,6 +114,43 @@ export default class Image extends Element {
59114
}
60115
}
61116

117+
/**
118+
* 设置九宫格参数
119+
*/
120+
setInsetParams(params: IInsetParams) {
121+
// 验证参数
122+
if (this.validateInsetParams(params)) {
123+
this.insetParams = { ...params };
124+
this.root?.emit('repaint');
125+
}
126+
}
127+
128+
/**
129+
* 验证九宫格参数
130+
*/
131+
private validateInsetParams(params: IInsetParams): boolean {
132+
if (!params) return false;
133+
134+
const { left, top, right, bottom } = params;
135+
136+
// 检查参数是否为非负数
137+
if (left < 0 || top < 0 || right < 0 || bottom < 0) {
138+
console.warn('Inset parameters must be non-negative');
139+
return false;
140+
}
141+
142+
// 如果有图片,检查参数是否超出图片尺寸
143+
if (this.img) {
144+
const { width: imgWidth, height: imgHeight } = this.img;
145+
if (left + right >= imgWidth || top + bottom >= imgHeight) {
146+
console.warn('Inset parameters exceed image dimensions');
147+
return false;
148+
}
149+
}
150+
151+
return true;
152+
}
153+
62154
repaint() {
63155
this.render();
64156
}
@@ -82,9 +174,18 @@ export default class Image extends Element {
82174

83175
const { needStroke, needClip, originX, originY, drawX, drawY, width, height } = this.baseRender();
84176

85-
// 自定义渲染逻辑 开始
86-
ctx.drawImage(this.img, drawX - originX, drawY - originY, width, height);
87-
// 自定义渲染逻辑 结束
177+
// 根据图片类型选择渲染方式
178+
switch (this.imageType) {
179+
case ImageType.SIMPLE:
180+
this.renderSimple(ctx, drawX, drawY, originX, originY, width, height);
181+
break;
182+
case ImageType.SLICED:
183+
this.renderSliced(ctx, drawX, drawY, originX, originY, width, height);
184+
break;
185+
case ImageType.TILED:
186+
this.renderTiled(ctx, drawX, drawY, originX, originY, width, height);
187+
break;
188+
}
88189

89190
if (needClip) {
90191
this.renderBorder(ctx, originX, originY);
@@ -95,8 +196,135 @@ export default class Image extends Element {
95196
}
96197

97198
ctx.translate(-originX, -originY);
199+
ctx.restore();
200+
}
201+
202+
/**
203+
* 普通渲染
204+
*/
205+
private renderSimple(ctx: CanvasRenderingContext2D, drawX: number, drawY: number, originX: number, originY: number, width: number, height: number) {
206+
ctx.drawImage(this.img!, drawX - originX, drawY - originY, width, height);
207+
}
208+
209+
/**
210+
* 九宫格渲染 - 图像会被拉伸以适应尺寸
211+
*/
212+
private renderSliced(ctx: CanvasRenderingContext2D, drawX: number, drawY: number, originX: number, originY: number, width: number, height: number) {
213+
if (!this.insetParams) {
214+
console.warn('九宫格渲染需要设置 insetParams 参数');
215+
this.renderSimple(ctx, drawX, drawY, originX, originY, width, height);
216+
return;
217+
}
98218

219+
const { left, top, right, bottom } = this.insetParams;
220+
const img = this.img!;
221+
const imgWidth = img.width;
222+
const imgHeight = img.height;
223+
224+
// 计算源区域尺寸
225+
const centerSrcWidth = imgWidth - left - right;
226+
const centerSrcHeight = imgHeight - top - bottom;
227+
228+
// 计算目标区域尺寸
229+
const targetCenterWidth = Math.max(0, width - left - right);
230+
const targetCenterHeight = Math.max(0, height - top - bottom);
231+
232+
const x = drawX - originX;
233+
const y = drawY - originY;
234+
235+
// 1. 渲染四个角(保持原样)
236+
this.renderSlicedCorners(ctx, img, x, y, width, height, imgWidth, imgHeight, left, top, right, bottom);
237+
238+
// 2. 渲染四条边(拉伸)
239+
this.renderSlicedEdges(ctx, img, x, y, width, height, imgWidth, imgHeight, left, top, right, bottom, centerSrcWidth, centerSrcHeight, targetCenterWidth, targetCenterHeight);
240+
241+
// 3. 渲染中心区域(拉伸)
242+
if (targetCenterWidth > 0 && targetCenterHeight > 0 && centerSrcWidth > 0 && centerSrcHeight > 0) {
243+
ctx.drawImage(img, left, top, centerSrcWidth, centerSrcHeight,
244+
x + left, y + top, targetCenterWidth, targetCenterHeight);
245+
}
246+
}
247+
248+
/**
249+
* 平铺渲染 - 图像按原始大小重复平铺
250+
*/
251+
private renderTiled(ctx: CanvasRenderingContext2D, drawX: number, drawY: number, originX: number, originY: number, width: number, height: number) {
252+
const img = this.img!;
253+
const x = drawX - originX;
254+
const y = drawY - originY;
255+
256+
// 使用 createPattern 进行平铺渲染
257+
const pattern = ctx.createPattern(img, 'repeat');
258+
if (!pattern) return;
259+
260+
ctx.save();
261+
262+
// 设置裁剪区域
263+
ctx.beginPath();
264+
ctx.rect(x, y, width, height);
265+
ctx.clip();
266+
267+
// 设置pattern并填充
268+
ctx.fillStyle = pattern;
269+
ctx.fillRect(x, y, width, height);
270+
99271
ctx.restore();
100272
}
101-
}
102273

274+
/**
275+
* 渲染九宫格的四个角
276+
*/
277+
private renderSlicedCorners(ctx: CanvasRenderingContext2D, img: HTMLImageElement, x: number, y: number, width: number, height: number, imgWidth: number, imgHeight: number, left: number, top: number, right: number, bottom: number) {
278+
// 左上角
279+
if (left > 0 && top > 0) {
280+
ctx.drawImage(img, 0, 0, left, top, x, y, left, top);
281+
}
282+
283+
// 右上角
284+
if (right > 0 && top > 0) {
285+
ctx.drawImage(img, imgWidth - right, 0, right, top,
286+
x + width - right, y, right, top);
287+
}
288+
289+
// 左下角
290+
if (left > 0 && bottom > 0) {
291+
ctx.drawImage(img, 0, imgHeight - bottom, left, bottom,
292+
x, y + height - bottom, left, bottom);
293+
}
294+
295+
// 右下角
296+
if (right > 0 && bottom > 0) {
297+
ctx.drawImage(img, imgWidth - right, imgHeight - bottom, right, bottom,
298+
x + width - right, y + height - bottom, right, bottom);
299+
}
300+
}
301+
302+
/**
303+
* 渲染九宫格的四条边
304+
*/
305+
private renderSlicedEdges(ctx: CanvasRenderingContext2D, img: HTMLImageElement, x: number, y: number, width: number, height: number, imgWidth: number, imgHeight: number, left: number, top: number, right: number, bottom: number, centerSrcWidth: number, centerSrcHeight: number, targetCenterWidth: number, targetCenterHeight: number) {
306+
// 上边 - 水平拉伸
307+
if (top > 0 && targetCenterWidth > 0) {
308+
ctx.drawImage(img, left, 0, centerSrcWidth, top,
309+
x + left, y, targetCenterWidth, top);
310+
}
311+
312+
// 下边 - 水平拉伸
313+
if (bottom > 0 && targetCenterWidth > 0) {
314+
ctx.drawImage(img, left, imgHeight - bottom, centerSrcWidth, bottom,
315+
x + left, y + height - bottom, targetCenterWidth, bottom);
316+
}
317+
318+
// 左边 - 垂直拉伸
319+
if (left > 0 && targetCenterHeight > 0) {
320+
ctx.drawImage(img, 0, top, left, centerSrcHeight,
321+
x, y + top, left, targetCenterHeight);
322+
}
323+
324+
// 右边 - 垂直拉伸
325+
if (right > 0 && targetCenterHeight > 0) {
326+
ctx.drawImage(img, imgWidth - right, top, right, centerSrcHeight,
327+
x + width - right, y + top, right, targetCenterHeight);
328+
}
329+
}
330+
}

0 commit comments

Comments
 (0)