NPoster 海报生成
传入 JSON 数据在离屏画布生成分享海报:色块、图片、文字、线条、二维码五类元素按层级合成一张图片,导出 png dataURL 可直接展示、预览或保存到相册。适合分享卡片、商品推广图、邀请卡、成绩单等营销与分享场景。
平台兼容性
| App | 微信小程序 | Web |
|---|---|---|
| ✓(编译通过,真机运行待验证) | ✓ | ✓ |
说明:
- 画布离屏放置(屏幕外),页面上看不到画布本体;生成结果为 png dataURL,由调用方决定展示(image)与保存(saveToAlbum)方式。
- 海报逻辑坐标系的宽高由
width/height决定,元素坐标均为该坐标系内的逻辑像素;导出图片的实际像素为逻辑尺寸 ×pixelRatio,打印分享建议保持默认 2 以上。 - 单个尺寸过大的画布在 iOS Safari 无法绘制(浏览器限制),画布逻辑尺寸建议控制在 2000px 以内。
- Web 端网络图片需支持跨域访问(组件已按跨域加载处理),否则画布被污染无法导出;使用本地图片与 dataURL 不受影响。
- 保存到相册:App 与微信小程序支持;Web 端不支持保存相册(触发 saveError),可在预览中长按保存。
基本使用
vue
<template>
<view>
<n-poster ref="posterRef" :width="600" :height="900" @ready="onReady" @error="onError" />
<image v-if="posterUrl" :src="posterUrl" mode="widthFix" />
<n-button text="生成海报" @click="onGenerate" />
</view>
</template>
<script setup lang="uts">
import { ref } from "vue";
import { type NPosterItems } from "@/uni_modules/new-ui/components/n-poster/props";
const posterRef = ref<NPosterComponentPublicInstance | null>(null);
const posterUrl = ref("");
const ready = ref(false);
function onReady() {
ready.value = true;
}
function onError(err : NPosterError) {
// 图片加载失败 / 二维码超容等:err.field + err.index + err.message
}
function onGenerate() {
if (!ready.value) return;
const items : NPosterItems = {
blocks : [
{ x : 0, y : 0, width : 600, height : 900, backgroundColor : "#ffffff" },
{ x : 24, y : 24, width : 120, height : 120, borderRadius : 24, backgroundColor : "#10b981" }
],
imgs : [
{ src : "/static/goods.png", x : 24, y : 180, width : 552, height : 400, borderRadius : 16, crop : true }
],
texts : [
{ text : "夏季新品上市", x : 24, y : 640, fontSize : 28, fontWeight : "bold", color : "#262626" },
{ text : "长按识别二维码了解更多", x : 24, y : 860, fontSize : 14, color : "#8c8c8c" }
],
lines : [
{ x : 24, y : 660, endX : 576, endY : 660, color : "#f0f0f0", width : 1 }
],
qrcodes : [
{ value : "https://example.com", x : 456, y : 760, width : 120, height : 120 }
]
};
posterRef.value?.generate(items).then((url : string) => {
if (url != "") posterUrl.value = url;
});
}
</script>
API
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| width | number | string | "750rpx" | 画布逻辑宽度,number 按 px,字符串支持 px / rpx(750rpx 即满屏宽) |
| height | number | string | "1024rpx" | 画布逻辑高度,number 按 px,字符串支持 px / rpx |
| pixelRatio | number | 2 | 导出倍率,导出图片实际像素 = 逻辑尺寸 × pixelRatio,范围 1~4 |
| customClass | string | "" | 挂到根节点的额外 class(类需定义在组件样式表内) |
| customStyle | string | "" | 根节点行内样式 |
事件
| 事件 | 参数 | 说明 |
|---|---|---|
| ready | — | 画布上下文就绪,可调用 generate |
| error | NPosterError | 生成失败:画布通道 / 图片加载失败(field 为 imgs,该项跳过其余照画)/ 二维码超容(field 为 qrcodes,不画该项) |
| saved | path: string | saveToAlbum 保存成功,参数为文件路径 |
| saveError | message: string | 保存失败(权限拒绝 / 写入失败 / 平台不支持) |
插槽
无。
实例方法
| 方法 | 签名 | 说明 |
|---|---|---|
| generate | (items: NPosterItems) => Promise<string> | 生成海报,成功 resolve png dataURL,失败 resolve 空串并触发 error |
| saveToAlbum | (dataURL: string) => Promise<string> | 保存到相册,成功 resolve 文件路径并触发 saved,失败 resolve 空串并触发 saveError |
数据项类型
NPosterItems = { blocks?, imgs?, texts?, lines?, qrcodes? },五类数组按 zIndex 升序绘制(相同层级保持传入顺序):
- NPosterText:text / x / y 必填;fontSize(14)、color("#000000")、baseLine("alphabetic")、textAlign("left")、width(0 不换行,大于 0 超宽折行)、rows(1,-1 不限,超出截断省略)、lineHeight(0 = 字号)、fontWeight、fontStyle、fontFamily("sans-serif")、opacity(1)、lineThrough(false 逐行划线)、frontText / frontSize / spacing(前置文本定位:不绘制前置文本本身,按其宽度把正文向后偏移,用于划线价等对照排版)
- NPosterBlock:x / y / width / height 必填;backgroundColor、gradientColor(两者搭配成渐变)、gradientType(1 上到下 / 2 左到右)、borderColor、borderWidth(0)、borderRadius(0,直径语义,等于宽高即圆形)、opacity(1)、shadow
- NPosterImage:src / x / y / width / height 必填;src 支持本地路径、网络路径、base64 dataURL;borderRadius(0 圆角裁剪)、borderWidth(0)、borderColor、crop(false 按原图比例居中裁满绘制区不变形)、zIndex
- NPosterLine:x / y / endX / endY 必填;color("#333333")、width(1)
- NPosterQrcode:value / x / y / width / height 必填;纠错级别固定 H;绘制区域取宽高较小值保持正方形(不拉伸模块);foreground("#000000")、background("#ffffff")
实现说明(参考做法 vs 本库做法)
| 机制 | 参考做法 | 本库做法 | 理由 |
|---|---|---|---|
| 画布体系 | 旧版 createCanvasContext + draw 回调,导出走 canvasToTempFilePath 临时文件 | canvas 2d 体系(Web 元素直取 / 小程序组件作用域 node / App 异步载体),同步绘制后 toDataURL 导出 png dataURL | 目标平台已无旧画布 API;dataURL 是库内画布族统一导出形态,可直接作 image src 预览 |
| 离屏画布 | fixed left:-6666px | 同参考 | 生成过程不干扰页面;微信 / Web / App 三端均可离屏绘制 |
| 图片来源 | type 区分:1 直用、2 网络图下载临时文件、3 base64 转本地文件(旧画布平台兼容手段),原始尺寸经 getImageInfo 获取 | 图片经画布 Image 通道直接加载绘制(网络 / 本地 / dataURL 通吃),原始尺寸从加载后的 Image 对象读取 | 新画布通道不需要预下载与转文件;加载失败按项报 error 并跳过,不整体失败 |
| crop 等比裁剪 | 按 imgWidth/imgHeight 三分支推源区 | 按原图与绘制区宽高比两分支居中裁源区 | 与参考等价、分支更少;等价于 image 组件 aspectFill 语义 |
| 渐变方向 | createLinearGradient 参数错位(把宽高当坐标传入),gradientType 1/2 实际画成对角线 | 1 = 上到下 (x,y,x,y+h)、2 = 左到右 (x,y,x+w,y) | 根治参考缺陷:渐变方向与 API 自述一致 |
| 文字基线 | baseLine 默认 'normal'(画布标准无此值,依赖平台容错) | 默认 'alphabetic'(等价意图的标准写法) | 根治参考缺陷:基线值显式合法 |
| 划线 | 只画一条且按全文实测宽定位,多行文本时划线错位 | 换行后逐行按行实测宽绘制 | 根治参考缺陷:多行划线与文字逐行对应 |
| 文字换行 | 逐字符累计测宽折行、rows 行截断省略 | 同参考算法,行数据附带实测宽(划线与行起点换算共用) | 机制一致;换行宽默认 0 = 不换行(参考默认 500rpx 强制测量,对短文本无感) |
| 前置文本 | frontText 不绘制,仅按其宽度偏移正文 x(划线价场景:现价项在前,划线价项声明 frontText 自动排后) | 同参考语义 | 划线价对照排版的定位辅助,机制一致 |
| 二维码 | 内置 qr 库生成点阵逐模块填充,矩形区域拉伸模块 | 复用库内二维码编码核心(QR 标准、纠错级别 H 与参考一致),绘制区取宽高较小值保持正方形 | 根治参考缺陷:非正方形区域拉伸模块会变形至无法识别;内容超容按项报 error 不画残码 |
| zIndex 排序 | Array.sort(相同 zIndex 顺序不稳定) | zIndex 升序 + 传入顺序稳定排序 | 同层级多次生成的叠放次序确定 |
| 层级默认色 | 二维码前景 "#181818" | "#000000" | 库内画布族默认纯黑一致性 |
| 保存相册 | 权限检查(App 查询权限 / 微信 authorize)+ 拒绝弹窗引导设置 + 组件内 toast | dataURL 写用户数据目录临时文件后 saveImageToPhotosAlbum,结果经 saved / saveError 事件回执 | 权限弹窗与提示归业务侧(组件不弹 toast / modal,库内一致性);平台拒绝原因通过 saveError 完整暴露 |
| removeSavedFile | 清理 saveFile 持久文件清单【备用】 | 不收录 | 本库写入的是用户数据目录临时文件(随小程序生命周期管理),不进 saveFile 持久清单,无参考所要清理的积累风险点 |
| 图片死参数 | API 表含 sx / sy / sw / sh 源裁剪参数,绘制引擎未实现 | 不收录 | 收录只会静默无效 |
| ready 时机 | mounted 后延时 50ms 创建画布上下文并触发 | 画布上下文真实就绪即触发(三端通道各自就绪) | 事件语义与实际可用性对齐,无固定延时补偿 |