# Painter 画板 测试版 > uniapp 海报画板,更优雅的海报生成方案 > [查看更多 站点1](https://limeui.qcoon.cn/#/painter) > [查看更多 站点2](http://liangei.gitee.io/limeui/#/painter) > Q群:806744170 ## 平台兼容 | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App | | --- | ---------- | ------------ | ---------- | ---------- | --------- | --- | | √ | √ | √ | 未测 | √ | √ | √ | ## 代码演示 ### 基本用法 - 插件提供JSON及HTML的方式绘制海报 - 插件参考了 css 块状流布局模拟css schema方式,放弃了之前使用的绝对定位布局。 #### 方式一 HTML - 插件提供了`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件 - 通过 `css` 属性绘制样式,与style使用方式保持一致。 因为style是保留字段,所以命名为`css`,如果有大佬知道如何破解请告之。 ```html ``` #### 方式二 JSON schema - 在json里四种类型组件的`type`为`view`、`text`、`image`、`qrcode` - 通过 `board` 设置海报所需的 JSON schema 数据进行绘制 - 所有类型的schema都具有`css`字段,css的样式属性key值使用驼峰命名如:`lineHeight` ```html ``` ```js data() { return { poster: { css: { // json 方式务必填写画板宽度 width: '750rpx' }, views: [ { css: { background: "#07c160", height: "120rpx", width: "120rpx", display: "inline-block" }, type: "view" }, { css: { background: "#1989fa", height: "120rpx", width: "120rpx", borderTopRightRadius: "60rpx", borderBottomLeftRadius: "60rpx", display: "inline-block", margin: "0 30rpx" }, views: [], type: "view" }, { css: { background: "#ff9d00", height: "120rpx", width: "120rpx", borderRadius: "50%", display: "inline-block" }, views: [], type: "view" }, ] } } } ``` ### View 容器 - 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode共同构建一颗完整的节点树 - 在JSON schema里具有 `views` 的数组字段,用于嵌套承载节点。 #### 方式一 HTML ```html ``` #### 方式二 JSON schema ```js { css: { width: '750rpx' }, views: [ { type: 'view', css: { background: '#f0f0f0', paddingTop: '100rpx' }, views: [ { type: 'view', css: { background: '#d9d9d9', width: '33.33%', height: '100rpx', display: 'inline-block' } }, { type: 'view', css: { background: '#bfbfbf', width: '66.66%', height: '100rpx', display: 'inline-block' } } ], } ] } ``` ### Text 文本 - 通过 `text` 属性填写文本内容。 - 支持`\n`换行符 - 支持省略号,使用css的`line-clamp`设置行数,当文字内容超过会显示省略号。 - 支持`text-decoration` #### 方式一 HTML ```html ``` #### 方式二 JSON schema ```js // 基础用法 { type: 'text', text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼', }, { type: 'text', text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼', css: { // 设置居中对齐 textAlign: 'center', // 设置中划线 textDecoration: 'line-through' } }, { type: 'text', text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼', css: { // 设置右对齐 textAlign: 'right', } }, { type: 'text', text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼', css: { // 设置行数,超出显示省略号 lineClamp: 3, } } ``` ### Image 图片 - 通过 `src` 属性填写图片路径。 - 图片路径支持:网络图片,本地static里的图片路径,缓存路径 - 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方CSS表格。 - 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方CSS表格。 - 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名 - 使用网络图片时:**H5需要决跨域问题** #### 方式一 HTML ```html ``` #### 方式二 JSON schema ```js // 基础用法 { type: 'image', src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg', css: { width: '200rpx', height: '200rpx' } }, // 填充方式 // css objectFit 设置 填充方式 见下方表格 { type: 'image', src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg', css: { width: '200rpx', height: '200rpx', objectFit: 'contain' } }, // css objectPosition 设置 图片的对齐方式 { type: 'image', src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg', css: { width: '200rpx', height: '200rpx', objectFit: 'contain', objectPosition: '50% 50%' } } ``` ### Qrcode 二维码 - 通过`text`属性填写需要生成二维码的文本。 - 通过 `css` 里的 `color` 可设置生成码点的颜色。 - 通过 `css` 里的 `background`可设置背景色。 - 通过 `css `里的 `width`、`height`设置尺寸。 #### 方式一 HTML ```html ``` #### 方式二 JSON schema ```js { type: 'qrcode', text: 'limeui.qcoon.cn', css: { width: '200rpx', height: '200rpx', } } ``` ### 生成图片 - 1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径 - 2、通过调用内部方法生成图片: ```html ...code ``` ```js // 主动调用方式只能在绘制完成之后 // @done 事件表示绘制完成 this.$refs.painter.canvasToTempFilePath({ // 在nvue里是jpeg fileType: 'jpg', quality: 1, success: (res) => { console.log(res.tempFilePath) } }) ``` ### 海报示例 - 提供一份示例,只把插件当成生成图片的工具,弹窗之类的功能另外寻找。 - 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径 - 使用`custom-style`样式把画板移到屏幕之外,因为可能canvas的层级比较高,无法覆盖。 - **注意**:海报画板不能隐藏 否则无法生成图片。 #### 方式一 HTML ```html ``` ```js data() { return { path: '' } } ``` #### 方式二 JSON schema ```html ``` ```js data() { return { path: '', poster: { css: { width: "750rpx", paddingBottom: "40rpx", background: "linear-gradient(,#000 0%, #ff5000 100%)" }, views: [ { src: "https://cdn.jsdelivr.net/gh/liangei/image@latest/avatar-1.jpeg", type: "image", css: { background: "#fff", objectFit: "cover", marginLeft: "40rpx", marginTop: "40rpx", width: "84rpx", border: "2rpx solid #fff", boxSizing: "border-box", height: "84rpx", borderRadius: "50%" } }, { type: "view", css: { marginTop: "40rpx", paddingLeft: "20rpx", display: "inline-block" }, views: [ { text: "隔壁老王", type: "text", css: { display: "block", paddingBottom: "10rpx", color: "#fff", fontSize: "32rpx", fontWeight: "bold" } }, { text: "为您挑选了一个好物", type: "text", css: { color: "rgba(255,255,255,.7)", fontSize: "24rpx" }, } ], }, { css: { marginLeft: "40rpx", marginTop: "30rpx", padding: "32rpx", boxSizing: "border-box", background: "#fff", borderRadius: "16rpx", width: "670rpx", boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)" }, views: [ { src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg", type: "image", css: { objectFit: "cover", objectPosition: "50% 50%", width: "606rpx", height: "606rpx" }, }, { css: { marginTop: "32rpx", color: "#FF0000", fontWeight: "bold", fontSize: "28rpx", lineHeight: "1em" }, views: [{ text: "¥", type: "text", css: { verticalAlign: "bottom" }, }, { text: "39", type: "text", css: { verticalAlign: "bottom", fontSize: "58rpx" }, }, { text: ".39", type: "text", css: { verticalAlign: "bottom" }, }, { text: "¥59.99", type: "text", css: { verticalAlign: "bottom", paddingLeft: "10rpx", fontWeight: "normal", textDecoration: "line-through", color: "#999999" } }], type: "view" }, { css: { marginTop: "32rpx", fontSize: "26rpx", color: "#8c5400" }, views: [{ text: "自营", type: "text", css: { color: "#212121", background: "#ffb400" }, }, { text: "30天最低价", type: "text", css: { marginLeft: "16rpx", background: "#fff4d9", textDecoration: "line-through" }, }, { text: "满减优惠", type: "text", css: { marginLeft: "16rpx", background: "#fff4d9" }, }, { text: "超高好评", type: "text", css: { marginLeft: "16rpx", background: "#fff4d9" }, }], type: "view" }, { css: { marginTop: "30rpx" }, views: [ { text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝", type: "text", css: { paddingRight: "32rpx", boxSizing: "border-box", lineClamp: 2, color: "#333333", lineHeight: "1.8em", fontSize: "36rpx", width: "478rpx" }, }, { text: "limeui.qcoon.cn", type: "qrcode", css: { width: "128rpx", height: "128rpx", }, }], type: "view" }], type: "view" } ] } } } ``` ### 原生小程序 - 插件里的`painter.js`支持在原生小程序中使用 - new Painter之后在`source`里传入JSON schema - 再调用`render`绘制海报 - 如需生成图片,请查看微信小程序cavnas的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html) ```html ``` ```js import {Painter} from './painter' page({ data: { poster: { css: { width: '750rpx' }, views: [ { type: 'view', css: { background: '#d2d4c8', paddingTop: '100rpx' }, views: [ { type: 'view', css: { background: '#5f7470', width: '33.33%', height: '100rpx', display: 'inline-block' } }, { type: 'view', css: { background: '#889696', width: '33.33%', height: '100rpx', display: 'inline-block' } }, { type: 'view', css: { background: '#b8bdb5', width: '33.33%', height: '100rpx', display: 'inline-block' } } ], } ] } }, async onLoad() { const res = await this.getCentext() const painter = new Painter(res) // 返回计算布局后的整个内容尺寸 const {width, height} = await painter.source(this.data.poster) // 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果 // 渲染 await painter.render() }, // 获取canvas 2d // 非2d也可以使用这里只是举个例子 getCentext() { return new Promise(resolve => { wx.createSelectorQuery() .select(`#painter`) .node() .exec(res => { let { node: canvas } = res[0]; resolve({ canvas, context: canvas.getContext('2d'), width: canvas.width, height: canvas.height, pixelRatio: 2 }) }) }) }, }) ``` ### Nvue - 插件是通过 `web-view` 支持 `app-nvue` - 默认是远端的文件,如果需要本地化,请按下方步骤: - 1、去码云把[hybrid](https://gitee.com/liangei/lime-painter/tree/master/examples/uni/hybrid)目录的文件,放到自己项目的根目录。 - 2、给插件文件`l-painter.vue`里`web-view`的`src`加上根目录的`hybrid路径`并注释`this.webViewInit()` ```html // 只加上路径 其它参数不要改 ``` ```js // 153行 注释该方法 // this.webViewInit() ``` ### 旧版更新(1.6.x) - 由于1.8.x版放弃了以定位的方式,所以1.6.x版更新之后要每个样式都加上`position: absolute` - 旧版的 `image` mode 模式被放弃,使用`object-fit` - 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-filePath` - 旧版的 `maxLines` 改成 `line-clamp` ## API ### Props | 参数 | 说明 | 类型 | 默认值 | | ------------- | ------------ | ---------------- | ------------ | | board | JSON schema方式的海报元素对象集 | object | - | | css | 海报最外层的样式,可以理解为`body` | object | 参数请向下看 | | custom-style | canvas自定义样式 | string | | | is-canvas-to-temp-filePath | 是否生成图片,在`@success`事件接收图片地址 | boolean | `false` | | after-delay | 生成图片错乱,可延时生成图片 | number | `100` | | type | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`、`` | string | `2d` | | file-type | 生成图片的后缀类型, 可选值:`png`、`jpg`,在nvue里是`jpeg` | string | `png` | | path-type | 生成图片路径类型,可选值`url`、`base64` | string | `-` | | pixel-ratio | 生成图片的像素密度,默认为对应手机的像素密度 | number | `-` | ### css | 属性名 | 支持的值或类型 | 默认值 | | ------------- | ------------ | ---------------- | | (min\max)width | 支持`%`、`rpx`、`px` | - | | height | 同上 | - | | color | `string` | - | | position | 定位,可选值:`absolute`、`fixed` | - | | ↳ left、top、right、bottom | 配合`position`才生效,支持`%`、`rpx`、`px` | - | | margin | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px` | - | | padding | 可简写或各方向分别写,支持`rpx`、`px` | - | | border | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写| - | | line-clamp | `number`,超过行数显示省略号 | - | | background | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | - | | vertical-align | 文字垂直对齐,可选值:`bottom`、`top`、`middle` | `middle` | | line-height | 文字行高,支持`rpx`、`px`、`em`| `1.4em` | | font-weight | 文字粗细,可选值:`normal`、`bold`| `normal` | | font-size | 文字大小,`string`,支持`rpx`、`px` | `14px` | | text-decoration | 文本修饰,可选值:`underline` 、`line-through`、`overline`| - | | text-align | 文本水平对齐,可选值:`right` 、`center`| - | | display | 框类型,可选值:`block`、`inline-block`、`none`,当为`none`时是不渲染该段 | - | | border-radius | 圆角边框,支持`%`、`rpx`、`px` | - | | box-sizing | 可选值:`border-box` | - | | box-shadow | 投影 | - | | background-image | view元素设置背景纹理,注意这里的背景纹理一般是用于纹理平铺,无法代替`image`元素。如:水印 | - | | background-repeat | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat` | `repeat` | | [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/) | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none` | - | | [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用 | - | ### 图片填充模式 object-fit | 名称 | 含义 | | ------- | ------------ | | contain | 保持宽高缩放图片,使图片的长边能完全显示出来 | | cover | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 | | fill | 拉伸图片,使图片填满元素 | | none | 保持图片原有尺寸 | ### 事件 Events | 事件名 | 说明 | 回调 | | ------- | ------------ | -------------- | | success | 生成图片成功,若使用了`is-canvas-to-temp-filePath` 可以接收图片地址 | path | | fail | 生成图片失败 | {error: error} | | done | 绘制成功 | | | progress | 绘制进度 | number | ## 常见问题 - 1、H5端使用网络图片需要解决跨域问题。 - 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配! - 3、H5端生成图片是base64,有时显示只有一半可以使用原生标签`` - 4、发生保存图片倾斜变形或提示native buffer exceed size limit时,使用pixel-ratio="2"参数,降分辨率。 - 5、h5保存图片不需要调接口,提示用户长按图片保存。 - 6、IOS APP 请勿使用HBX2.9.3.20201014的版本!这个版本无法生成图片。 - 7、组件不能隐藏,包含`v-if`,`v-show`、`display:none`、`opacity:0` - 8、微信小程序 canvas 2d不支持真机调试,请使用真机预览方式。 - 9、华为手机APP上无法生成图片,请使用HBX2.9.11++ - 10、苹果微信7.0.20存在闪退和图片无法onload为微信bug,请到码云上升级本插件 ## 打赏 如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。 ![输入图片说明](https://cdn.jsdelivr.net/gh/liangei/image@latest/222521_bb543f96_518581.jpeg "微信图片编辑_20201122220352.jpg")