Skip to content

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&lt;string> 生成海报,成功 resolve png dataURL,失败 resolve 空串并触发 error
saveToAlbum (dataURL: string) => Promise&lt;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 创建画布上下文并触发 画布上下文真实就绪即触发(三端通道各自就绪) 事件语义与实际可用性对齐,无固定延时补偿

记录 · 思考 · 创作