ste-canvas-poster 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/LICENSE +15 -0
- package/README.md +543 -0
- package/index.d.ts +23 -0
- package/index.js +4 -0
- package/package.json +143 -0
- package/src/measureText.js +53 -0
- package/src/posterAdapter.js +400 -0
- package/src/posterEngine.js +946 -0
- package/src/qrcodeGenerator.js +533 -0
- package/types.d.ts +198 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 1.0.0 (2026-05-26)
|
|
6
|
+
|
|
7
|
+
### 首次发布
|
|
8
|
+
|
|
9
|
+
- Schema 驱动的声明式海报绘制引擎
|
|
10
|
+
- 支持微信小程序与 APP 双端
|
|
11
|
+
- 内置元素类型:view、image、text、qrcode
|
|
12
|
+
- Flex 布局支持(row / column、alignItems、justifyContent)
|
|
13
|
+
- 模板变量自动替换 `{{key}}`
|
|
14
|
+
- rpx 到 px 自动转换
|
|
15
|
+
- 渐变背景(linear-gradient)
|
|
16
|
+
- 圆角/边框/阴影
|
|
17
|
+
- 文本多行换行、省略号、删除线
|
|
18
|
+
- 图片 objectFit(fill / cover / contain)
|
|
19
|
+
- 内置二维码生成,无需额外依赖
|
|
20
|
+
- 完整 TypeScript 类型声明
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# ste-canvas-poster
|
|
2
|
+
|
|
3
|
+
基于 Canvas 2D API 的声明式海报绘制引擎,支持微信小程序与 APP 双端。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **Schema 驱动** — 通过 JSON 描述海报结构,数值直接使用设计稿 rpx
|
|
8
|
+
- **双端一致** — 一套 Schema 同时适配微信小程序与 APP,无需条件编译
|
|
9
|
+
- **模板变量** — Schema 中 `{{key}}` 自动替换为 data 数据
|
|
10
|
+
- **Flex 布局** — view 容器支持 `display: 'flex'` 及子元素 margin
|
|
11
|
+
- **内置二维码** — qrcode 类型直接生成二维码,无需额外依赖
|
|
12
|
+
- **TypeScript 支持** — 完整类型声明,开发时自动补全
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install ste-canvas-poster
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
### 1. 定义 Schema
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const schema = {
|
|
26
|
+
width: 750,
|
|
27
|
+
height: 1068,
|
|
28
|
+
background: "#FFFFFF",
|
|
29
|
+
views: [
|
|
30
|
+
{
|
|
31
|
+
type: "view",
|
|
32
|
+
css: {
|
|
33
|
+
left: 54,
|
|
34
|
+
top: 54,
|
|
35
|
+
width: 642,
|
|
36
|
+
height: 960,
|
|
37
|
+
borderRadius: 20,
|
|
38
|
+
background: "#FFFFFF",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: "image",
|
|
43
|
+
src: "{{coverImage}}",
|
|
44
|
+
css: {
|
|
45
|
+
left: 96,
|
|
46
|
+
top: 160,
|
|
47
|
+
width: 560,
|
|
48
|
+
height: 478,
|
|
49
|
+
borderRadius: 12,
|
|
50
|
+
objectFit: "cover",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: "{{titleText}}",
|
|
56
|
+
css: {
|
|
57
|
+
left: 96,
|
|
58
|
+
top: 730,
|
|
59
|
+
fontSize: 32,
|
|
60
|
+
fontWeight: "bold",
|
|
61
|
+
color: "#181818",
|
|
62
|
+
maxWidth: 560,
|
|
63
|
+
ellipsis: true,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: "qrcode",
|
|
68
|
+
src: "{{qrcodeUrl}}",
|
|
69
|
+
css: { left: 504, top: 842, width: 160, height: 160 },
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. 准备数据
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const data = {
|
|
79
|
+
coverImage: "https://example.com/cover.jpg",
|
|
80
|
+
titleText: "精选商品",
|
|
81
|
+
qrcodeUrl: "https://example.com",
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. 渲染与保存
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
import { renderPoster } from "ste-canvas-poster";
|
|
89
|
+
|
|
90
|
+
// 渲染
|
|
91
|
+
const engine = await renderPoster({
|
|
92
|
+
schema,
|
|
93
|
+
data,
|
|
94
|
+
selector: "#myCanvas",
|
|
95
|
+
vm: this,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// 保存到相册
|
|
99
|
+
await engine.saveToAlbum();
|
|
100
|
+
|
|
101
|
+
// 或获取临时路径(用于分享)
|
|
102
|
+
const tempPath = await engine.toTempFilePath();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 4. Template 模板
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<template>
|
|
109
|
+
<!-- #ifdef MP-WEIXIN -->
|
|
110
|
+
<canvas id="myCanvas" type="2d" class="canvas" :style="canvasStyle" />
|
|
111
|
+
<!-- #endif -->
|
|
112
|
+
<!-- #ifdef APP-PLUS -->
|
|
113
|
+
<canvas
|
|
114
|
+
canvas-id="myCanvas"
|
|
115
|
+
id="myCanvas"
|
|
116
|
+
class="canvas"
|
|
117
|
+
:style="canvasStyle"
|
|
118
|
+
/>
|
|
119
|
+
<!-- #endif -->
|
|
120
|
+
</template>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
computed: {
|
|
125
|
+
canvasStyle() {
|
|
126
|
+
return { width: '750rpx', height: '1068rpx' };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## API
|
|
134
|
+
|
|
135
|
+
### renderPoster(options)
|
|
136
|
+
|
|
137
|
+
渲染海报,自动处理路径解析、rpx 转换和平台差异。
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
function renderPoster(options: RenderPosterOptions): Promise<PosterEngine>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**参数**:
|
|
144
|
+
|
|
145
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
146
|
+
| ---------- | --------------------- | ---- | -------- | ------------------------------------- |
|
|
147
|
+
| `schema` | `PosterSchema` | 是 | - | 海报结构描述 |
|
|
148
|
+
| `data` | `TemplateData` | 否 | `{}` | 模板变量数据 |
|
|
149
|
+
| `selector` | `string` | 是 | - | Canvas 选择器,如 `'#myCanvas'` |
|
|
150
|
+
| `vm` | `Record<string, any>` | 是 | - | Vue 组件实例 |
|
|
151
|
+
| `dpr` | `number` | 否 | 自动获取 | 像素比 |
|
|
152
|
+
| `useRpx` | `boolean` | 否 | `true` | 是否将 schema 数值视为 rpx 并自动转换 |
|
|
153
|
+
|
|
154
|
+
**返回值**:`Promise<PosterEngine>` — 引擎实例
|
|
155
|
+
|
|
156
|
+
### PosterEngine
|
|
157
|
+
|
|
158
|
+
渲染完成后返回的引擎实例,提供以下方法:
|
|
159
|
+
|
|
160
|
+
#### engine.saveToAlbum()
|
|
161
|
+
|
|
162
|
+
导出图片并保存到系统相册。
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
saveToAlbum(): Promise<string>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**返回值**:临时文件路径
|
|
169
|
+
|
|
170
|
+
#### engine.toTempFilePath(options?)
|
|
171
|
+
|
|
172
|
+
导出为临时文件路径,不保存到相册。
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
toTempFilePath(options?: ToTempFilePathOptions): Promise<string>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
179
|
+
| ---------- | ---------------- | ------- | ---------------------------- |
|
|
180
|
+
| `fileType` | `'png' \| 'jpg'` | `'png'` | 导出格式 |
|
|
181
|
+
| `quality` | `number` | `1` | 图片质量(0-1),仅 jpg 有效 |
|
|
182
|
+
|
|
183
|
+
**返回值**:临时文件路径
|
|
184
|
+
|
|
185
|
+
#### engine.destroy()
|
|
186
|
+
|
|
187
|
+
销毁引擎实例,释放图片缓存等资源。组件卸载时调用。
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
destroy(): void
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 工具函数
|
|
194
|
+
|
|
195
|
+
#### rpx2px(rpx)
|
|
196
|
+
|
|
197
|
+
将 rpx 转换为 px(基于当前屏幕宽度)。
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
function rpx2px(rpx: number): number;
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### px2rpx(px)
|
|
204
|
+
|
|
205
|
+
将 px 转换为 rpx。
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
function px2rpx(px: number): number;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### getCanvasNode(selector, vm)
|
|
212
|
+
|
|
213
|
+
获取 Canvas 节点(双端统一封装)。
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
function getCanvasNode(selector: string, vm: Record<string, any>): Promise<any>;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### measureText(text, fontSize, bold?)
|
|
220
|
+
|
|
221
|
+
计算文本渲染宽度,考虑中英文、数字、符号的宽度差异。
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
function measureText(text: string, fontSize: number, bold?: boolean): number;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
228
|
+
| ---------- | --------- | ---- | ------- | ------------------- |
|
|
229
|
+
| `text` | `string` | 是 | - | 文本内容 |
|
|
230
|
+
| `fontSize` | `number` | 是 | - | 字号(rpx) |
|
|
231
|
+
| `bold` | `boolean` | 否 | `false` | 是否加粗(加宽 6%) |
|
|
232
|
+
|
|
233
|
+
**返回值**:文本宽度(rpx),向上取整
|
|
234
|
+
|
|
235
|
+
典型用途 — 配合 Flex 布局计算容器宽度:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
import { measureText } from 'ste-canvas-poster';
|
|
239
|
+
|
|
240
|
+
const textWidth = Math.min(measureText(nickname, 24), 300);
|
|
241
|
+
// 在 schema 中使用
|
|
242
|
+
{ type: 'text', text: '{{nickname}}', css: { width: textWidth, height: 24, fontSize: 24 } }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Schema 规范
|
|
248
|
+
|
|
249
|
+
### 根节点
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
{
|
|
253
|
+
width: 750, // 画布宽度(rpx)
|
|
254
|
+
height: 1068, // 画布高度(rpx)
|
|
255
|
+
borderRadius: 24, // 画布圆角(可选,导出圆角图片)
|
|
256
|
+
background: '#FFFFFF', // 背景色或渐变(可选,不设置则透明)
|
|
257
|
+
backgroundImage: '{{bg}}', // 背景图(可选,支持模板变量,加载失败回退到 background)
|
|
258
|
+
views: [] // 元素列表,按顺序绘制
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**背景配置**:
|
|
263
|
+
|
|
264
|
+
| 配置 | 效果 |
|
|
265
|
+
| ----------------------------------------------------------------- | ------------------------- |
|
|
266
|
+
| 不设置 `background` 和 `backgroundImage` | 透明,导出 PNG 带透明通道 |
|
|
267
|
+
| `background: '#FFFFFF'` | 白色背景 |
|
|
268
|
+
| `background: 'linear-gradient(180deg, #FF0000 0%, #FFFFFF 100%)'` | 渐变背景 |
|
|
269
|
+
| `backgroundImage: '{{bg}}'` | 背景图 |
|
|
270
|
+
|
|
271
|
+
**圆角说明**:设置 `borderRadius` 后,整个画布被裁剪为圆角矩形,圆角外区域透明。
|
|
272
|
+
|
|
273
|
+
### 元素类型
|
|
274
|
+
|
|
275
|
+
#### view — 容器
|
|
276
|
+
|
|
277
|
+
支持背景、边框、圆角和 Flex 布局,可嵌套子元素。
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
{
|
|
281
|
+
type: 'view',
|
|
282
|
+
css: {
|
|
283
|
+
left: 54,
|
|
284
|
+
top: 54,
|
|
285
|
+
width: 642,
|
|
286
|
+
height: 960,
|
|
287
|
+
borderRadius: 20,
|
|
288
|
+
background: '#FFFFFF',
|
|
289
|
+
borderWidth: 2,
|
|
290
|
+
borderColor: '#CCCCCC'
|
|
291
|
+
},
|
|
292
|
+
views: [] // 子元素
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**渐变背景**:
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
background: "linear-gradient(180deg, #EE3C3C 0%, #FFFFFF 100%)";
|
|
300
|
+
// 角度遵循 CSS 规范:0deg 从下到上,90deg 从左到右,180deg 从上到下
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Flex 布局**:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
{
|
|
307
|
+
type: 'view',
|
|
308
|
+
css: {
|
|
309
|
+
left: 54, top: 54, width: 642, height: 200,
|
|
310
|
+
background: '#F5F5F5',
|
|
311
|
+
borderRadius: 12,
|
|
312
|
+
display: 'flex',
|
|
313
|
+
flexDirection: 'row',
|
|
314
|
+
alignItems: 'center',
|
|
315
|
+
justifyContent: 'center',
|
|
316
|
+
padding: [20, 30] // [上下, 左右] 或 [上, 右, 下, 左]
|
|
317
|
+
},
|
|
318
|
+
views: [
|
|
319
|
+
{ type: 'image', css: { width: 30, height: 30, borderRadius: 15, marginRight: 8 } },
|
|
320
|
+
{ type: 'text', text: '{{name}}', css: { fontSize: 24, width: 100, height: 24 } }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### image — 图片
|
|
326
|
+
|
|
327
|
+
绘制图片,支持圆角裁剪和 objectFit。
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
{
|
|
331
|
+
type: 'image',
|
|
332
|
+
src: '{{coverImage}}', // 支持模板变量
|
|
333
|
+
css: {
|
|
334
|
+
left: 96,
|
|
335
|
+
top: 160,
|
|
336
|
+
width: 560,
|
|
337
|
+
height: 478,
|
|
338
|
+
borderRadius: 12,
|
|
339
|
+
objectFit: 'cover' // 'fill' | 'cover' | 'contain'
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### text — 文本
|
|
345
|
+
|
|
346
|
+
绘制文本,支持多行换行、省略号、删除线。
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
{
|
|
350
|
+
type: 'text',
|
|
351
|
+
text: '{{titleText}}', // 支持模板变量
|
|
352
|
+
css: {
|
|
353
|
+
left: 96,
|
|
354
|
+
top: 730,
|
|
355
|
+
fontSize: 32,
|
|
356
|
+
fontWeight: 'bold', // 'normal' | 'bold' | '400' | '700'
|
|
357
|
+
fontFamily: 'sans-serif',
|
|
358
|
+
color: '#181818',
|
|
359
|
+
textAlign: 'left', // 'left' | 'center' | 'right'
|
|
360
|
+
lineHeight: 1.4, // 行高倍数(不是 px)
|
|
361
|
+
maxWidth: 560, // 超过则换行或省略
|
|
362
|
+
ellipsis: true, // 单行省略号
|
|
363
|
+
lines: 2 // 最大行数(0 = 不限)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### qrcode — 二维码
|
|
369
|
+
|
|
370
|
+
生成并绘制二维码,内容支持模板变量。
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
{
|
|
374
|
+
type: 'qrcode',
|
|
375
|
+
text: '{{qrcodeUrl}}', // 或 src: '{{qrcodeUrl}}'
|
|
376
|
+
css: {
|
|
377
|
+
left: 504,
|
|
378
|
+
top: 842,
|
|
379
|
+
width: 160,
|
|
380
|
+
height: 160,
|
|
381
|
+
color: '#000000', // 前景色
|
|
382
|
+
background: '#FFFFFF' // 背景色
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## CSS 属性参考
|
|
390
|
+
|
|
391
|
+
### 通用属性
|
|
392
|
+
|
|
393
|
+
所有元素均可使用:
|
|
394
|
+
|
|
395
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
396
|
+
| -------------- | -------------------- | ------ | ------------------------------ |
|
|
397
|
+
| `left` | `number` | `0` | x 坐标 |
|
|
398
|
+
| `top` | `number` | `0` | y 坐标 |
|
|
399
|
+
| `right` | `number` | - | 右侧定位 |
|
|
400
|
+
| `bottom` | `number` | - | 底部定位 |
|
|
401
|
+
| `width` | `number` | - | 宽度 |
|
|
402
|
+
| `height` | `number` | - | 高度 |
|
|
403
|
+
| `opacity` | `number` | `1` | 透明度(0-1),不参与 rpx 缩放 |
|
|
404
|
+
| `borderRadius` | `number \| number[]` | `0` | 圆角,支持 `[lt, rt, rb, lb]` |
|
|
405
|
+
|
|
406
|
+
### view 属性
|
|
407
|
+
|
|
408
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
409
|
+
| ----------------- | --------------------------------------------- | -------------- | -------------- |
|
|
410
|
+
| `background` | `string` | - | 背景色或渐变 |
|
|
411
|
+
| `backgroundColor` | `string` | - | 同 background |
|
|
412
|
+
| `borderWidth` | `number` | - | 边框宽度 |
|
|
413
|
+
| `borderColor` | `string` | - | 边框颜色 |
|
|
414
|
+
| `display` | `'flex'` | - | 启用 Flex 布局 |
|
|
415
|
+
| `flexDirection` | `'row' \| 'column'` | `'row'` | 主轴方向 |
|
|
416
|
+
| `alignItems` | `'flex-start' \| 'center' \| 'flex-end'` | `'flex-start'` | 交叉轴对齐 |
|
|
417
|
+
| `justifyContent` | `'flex-start' \| 'center' \| 'space-between'` | `'flex-start'` | 主轴对齐 |
|
|
418
|
+
| `padding` | `number \| number[]` | `0` | 内边距 |
|
|
419
|
+
|
|
420
|
+
### image 属性
|
|
421
|
+
|
|
422
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
423
|
+
| ----------- | -------------------------------- | -------- | -------- |
|
|
424
|
+
| `objectFit` | `'fill' \| 'cover' \| 'contain'` | `'fill'` | 缩放模式 |
|
|
425
|
+
|
|
426
|
+
### text 属性
|
|
427
|
+
|
|
428
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
429
|
+
| ---------------- | ------------------------------- | -------------- | ------------------------- |
|
|
430
|
+
| `fontSize` | `number` | `14` | 字号 |
|
|
431
|
+
| `fontWeight` | `string \| number` | `'normal'` | 字重 |
|
|
432
|
+
| `fontFamily` | `string` | `'sans-serif'` | 字体 |
|
|
433
|
+
| `color` | `string` | `'#000000'` | 文本颜色 |
|
|
434
|
+
| `textAlign` | `'left' \| 'center' \| 'right'` | `'left'` | 对齐方式 |
|
|
435
|
+
| `lineHeight` | `number` | `1.4` | 行高倍数,不参与 rpx 缩放 |
|
|
436
|
+
| `maxWidth` | `number` | - | 最大宽度 |
|
|
437
|
+
| `ellipsis` | `boolean` | `false` | 单行省略号 |
|
|
438
|
+
| `lines` | `number` | `0` | 最大行数,不参与 rpx 缩放 |
|
|
439
|
+
| `textDecoration` | `'line-through' \| 'none'` | - | 文本装饰 |
|
|
440
|
+
|
|
441
|
+
### Flex 子元素 margin
|
|
442
|
+
|
|
443
|
+
Flex 布局中子元素支持的间距属性:
|
|
444
|
+
|
|
445
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
446
|
+
| -------------- | -------- | ------ | ------ |
|
|
447
|
+
| `marginLeft` | `number` | `0` | 左间距 |
|
|
448
|
+
| `marginRight` | `number` | `0` | 右间距 |
|
|
449
|
+
| `marginTop` | `number` | `0` | 上间距 |
|
|
450
|
+
| `marginBottom` | `number` | `0` | 下间距 |
|
|
451
|
+
|
|
452
|
+
### 不参与 rpx 缩放的字段
|
|
453
|
+
|
|
454
|
+
| 字段 | 语义 |
|
|
455
|
+
| ------------ | -------------------------- |
|
|
456
|
+
| `opacity` | 透明度 0~1 |
|
|
457
|
+
| `lines` | 行数 |
|
|
458
|
+
| `flex` | flex 比例 |
|
|
459
|
+
| `lineHeight` | 行高倍数 |
|
|
460
|
+
| `fontWeight` | 字重(数字形式如 400/700) |
|
|
461
|
+
| `zIndex` | 层叠顺序 |
|
|
462
|
+
| `dpr` | 像素比 |
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 命名规范
|
|
467
|
+
|
|
468
|
+
所有 Schema 及 CSS 属性统一使用 **camelCase**(驼峰命名):
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
// ✅ 正确
|
|
472
|
+
{ css: { fontSize: 32, borderRadius: 12, objectFit: 'cover' } }
|
|
473
|
+
|
|
474
|
+
// ❌ 错误
|
|
475
|
+
{ css: { 'font-size': 32, 'border-radius': 12, 'object-fit': 'cover' } }
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 平台差异
|
|
481
|
+
|
|
482
|
+
| 特性 | 微信小程序 | APP |
|
|
483
|
+
| ------------- | ----------------------- | -------------------------- |
|
|
484
|
+
| Canvas 初始化 | `canvas.createImage()` | `uni.createCanvasContext` |
|
|
485
|
+
| 图片路径 | 直接使用网络 URL | 需先 `downloadFile` 到本地 |
|
|
486
|
+
| rpx 缩放 | CSS 自动处理,scale = 1 | scale = windowWidth / 750 |
|
|
487
|
+
| 图片加载 | `canvas.createImage()` | `uni.getImageInfo` |
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## 常见问题
|
|
492
|
+
|
|
493
|
+
### 海报只显示一部分
|
|
494
|
+
|
|
495
|
+
Canvas 内部尺寸小于元素坐标范围。确保 `canvas.width/height` 不小于最大元素坐标,小程序端保持设计稿尺寸。
|
|
496
|
+
|
|
497
|
+
### 文字大小与预期不符
|
|
498
|
+
|
|
499
|
+
`fontSize` 单位为 rpx,确保 `useRpx: true`(默认值)。
|
|
500
|
+
|
|
501
|
+
### 图片加载失败
|
|
502
|
+
|
|
503
|
+
数据中的图片路径字段名需以 `Image`/`Img`/`Url`/`Src`/`Photo`/`Pic` 结尾,或完整匹配 `background`/`qrcode`/`cover`/`avatar`,引擎才会自动解析路径。
|
|
504
|
+
|
|
505
|
+
### Flex 布局中 text 子元素位置不正确
|
|
506
|
+
|
|
507
|
+
Canvas 无法在绘制前测量文本尺寸,Flex 布局依赖子元素显式声明的 `width`/`height` 来计算位置。text 子元素若未设置,会被视为尺寸 0。
|
|
508
|
+
|
|
509
|
+
**解决**:为 text 子元素显式设置 `width` 和 `height`,配合 `measureText` 动态计算宽度:
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
import { measureText } from 'ste-canvas-poster';
|
|
513
|
+
|
|
514
|
+
const textWidth = Math.min(measureText(nickname, 24), 300);
|
|
515
|
+
{ type: 'text', text: '{{nickname}}', css: { width: textWidth, height: 24 } }
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
若不方便预计算宽度,可改用绝对定位(`left`/`top`)替代 Flex 布局。
|
|
519
|
+
|
|
520
|
+
### 导出图片圆角看起来不生效
|
|
521
|
+
|
|
522
|
+
画布默认背景是透明的,圆角外区域也是透明。如果查看器/聊天窗口是白底,透明区域和白底融为一体看起来像没有圆角。设置 `background: '#FFFFFF'` 即可明显看到圆角效果。
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## 文件结构
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
ste-canvas-poster/
|
|
530
|
+
├── index.js # 导出入口
|
|
531
|
+
├── index.d.ts # 函数类型声明
|
|
532
|
+
├── types.d.ts # Schema / CSS 类型声明
|
|
533
|
+
├── src/
|
|
534
|
+
│ ├── posterAdapter.js # 双端适配:Canvas 初始化、rpx 转换、图片预下载
|
|
535
|
+
│ ├── posterEngine.js # 绘制引擎:Schema 解析、Canvas 绘制
|
|
536
|
+
│ ├── qrcodeGenerator.js # 二维码生成
|
|
537
|
+
│ └── measureText.js # 文本宽度计算
|
|
538
|
+
└── package.json
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## License
|
|
542
|
+
|
|
543
|
+
ISC
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────
|
|
2
|
+
// 导出的函数
|
|
3
|
+
// ─────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import { RenderPosterOptions, PosterEngine } from './types';
|
|
6
|
+
|
|
7
|
+
/** 渲染海报(一步到位,自动处理路径解析和平台差异) */
|
|
8
|
+
export function renderPoster(options: RenderPosterOptions): Promise<PosterEngine>;
|
|
9
|
+
|
|
10
|
+
/** 获取 Canvas 节点(双端统一封装) */
|
|
11
|
+
export function getCanvasNode(selector: string, vm: Record<string, any>): Promise<any>;
|
|
12
|
+
|
|
13
|
+
/** 将 rpx 转换为 px(基于当前屏幕宽度) */
|
|
14
|
+
export function rpx2px(rpx: number): number;
|
|
15
|
+
|
|
16
|
+
/** 将 px 转换为 rpx */
|
|
17
|
+
export function px2rpx(px: number): number;
|
|
18
|
+
|
|
19
|
+
/** 计算文本渲染宽度(考虑中英文、数字、符号的宽度差异) */
|
|
20
|
+
export function measureText(text: string, fontSize: number, bold?: boolean): number;
|
|
21
|
+
|
|
22
|
+
/** 加载图片(双端统一封装,返回包含 width/height/path 的图片对象) */
|
|
23
|
+
export function loadImage(canvas: any, src: string): Promise<any>;
|
package/index.js
ADDED