- 小程序/小游戏动态生成分享海报 - 技术方案分享
在应用开发过程中,我们会遇到各种各样的分享场景,例如邀请、拉新、分享内容等。分享链接是 Web 时代常见的分享形式,实现也相对容易。但是现在人们时间大都花在了 APP 上,所以应用之间的分享越来越重要,然而应用之间分享链接却不是那么顺利和有效果。往往受以下制约: 纯文字链接,依靠文字向外界传达信息,信息量小、可信度低。群里丢了一个链接进来,什么描述都没有,大多数人都不会去点。链接的描述一般也不会太长,信息不会太多。分享的目标应用的外链策略。淘宝购物链接不能分享到微信、营销链接容易被微信封禁,都是受微信外链策略的影响。平台分享机制限制。小程序的转发功能允许用户直接将小程序分享到联系人中,却无法分享到朋友圈。若开发者希望用户可以分享小程序到朋友圈中,通常需要生成分享海报图片,分享图片到朋友圈中。所以 APP、H5、小程序等应用中分享功能,除了实现分享链接以外,还需要生成分享海报图片,在图片上展现更丰富的内容,一图胜千言。 如何低成本地生成内容丰富的海报图片,就是我们想要解决的问题。 常见技术方案从生成图片的位置划分,可以将方案划分为两种:客户端生成、服务端生成。 在客户端生成图片是将图片中的元素都下载到本地,然后使用绘图 API 进行绘制,典型方案就是使用 Canvas 来绘制图片。 在服务端生成图片,又可以分为两种,一种是在服务端使用绘图库绘制,然后返回图片或图片链接给客户端;另一种是在服务端使用HTML + CSS 生成带有样式的网页,然后使用无头浏览器截图,返回图片或图片链接给客户端。 简而言之,一般会使用下面这三种方式: 在客户端使用 Canvas 生成图片在服务器上使用网页完成样式渲染,然后截图返回给客户端使用后端绘图库绘制,然后返回给客户端下面逐一分析各种方案生成海报图片的优缺点。 客户端使用 Canvas 生成海报图片优点: 渲染过程在每个客户端中完成,渲染相对独立,基本上不需要考虑并发的问题。Canvas 特性丰富,可以实现样式复杂的图片渲染。缺点也很明显: 上手门槛高,需要灵活使用 Canvas API代码可读性差,调试过程复杂。代码复用程度低,每个端都需要重新编码。客户端型号众多,用户设备上的表现还可能与在开发机上的表现存在差异。兼容性太差,这是客户端渲染最大的痛点。如果有远程图片,可能会因跨域,无法下载,导致绘制失败。推荐阅读:小程序canvas绘制海报 服务端浏览器,网页截图在服务器上使用 HTML + CSS 在无头浏览器中完成网页样式布局与内容填充,然后使用无头浏览器提供的截图 API,将生成的网页截图保存。无头浏览器一般会选用 [代码]Puppeteer[代码]。 [代码]Puppeteer[代码] 是谷歌官方团队开发的一个 Node.js 库,它提供了一些 API 用来控制浏览器的行为,比如打开网页、模拟输入、点击按钮、屏幕截图等操作,通过这些 API 可以完成很多有趣的事情,比如本文要讲的海报渲染服务,就会用到屏幕截图功能。 这种方案的优缺点也很明显。 优点: 上手简单,只需要了解 HTML 、CSS 就可以代码可读性高,易于调试得力于HTML、CSS,表达力强。只要在网页上能实现,就可以应用到海报图片中。返回给客户端的是图片链接,不用考虑兼容性。服务端生成图片带来的最大好处是多端兼容。但这也会引入一个问题,成本高。 在后端需要运行一个 Node 服务来跑[代码]Puppeteer[代码] 控制一个浏览器,性能太低。一个4核16G内存的服务器生成图片,峰值QPS只有 10-20,在较差情况下每秒只能生成10张图片。 推荐阅读:使用 Puppeteer 搭建统一海报渲染服务 服务端绘图库绘制图片在服务端中,使用绘图库,绘制图片,然后将图片保存至 CDN 中,再返回图片链接给客户端。常用编程语言都有绘图库,例如 PHP 的 GD 库。相较于控制浏览器截图,这个方案的性能更高,也具有服务端渲染的好处,但灵活性却没有使用CSS控制样式高。 优点: 性能高服务端架构统一,开发者不用单独维护一个Node.js 服务。代码可读性高缺点: 复杂样式,开发时间长,需要微调。自适应布局困难。推荐阅读:PHP 使用GD库合成带二维码的海报步骤以及源码实现 上面介绍了三种生成分享海报图片的常用方案,了解了实现原理。开发者在实现这些方案时都需要进行独立的开发,维护复杂的样式代码,每增加一种海报,就需要维护一份样式代码。 海报只是业务中很小的一环,自己维护一个海报渲染服务,付出的成本与收益之间不成正比。所以我们更推荐使用第三方海报/图片渲染服务,来完成实现我们的想法。 imgrender.cn 动态图片渲染服务Imgrender 是一个免费的图片渲染服务,通过一个API,根据配置动态渲染图片,快速生成不同内容的图片。渲染模板配置简单,特别适合拥有不同分享海报的应用,快速、动态地生成分享海报。Imgrender 支持「文本」、「图片」、「二维码」、「矩形」、「线段」五种组件,可满足绝大多数海报的渲染需求。 👏 免费📝 API 优先🛣 动态渲染,内容可动态调整🖥 易于配置,方便调试📱 兼容性高,渲染结果只与配置有关⚡️ 快速、稳定,平均响应时间 400msImgrender 只有一个核心 API,通过 API 传递海报内容,就可以动态生成不同内容的图片。海报内容完全配置化,在完成设计稿后,按照设计尺寸和位置生成配置即可。 使用服务也很简单,只需要请求 [代码]POST https://api.imgrender.net/open/v1/pics[代码]。将下面的 curl 命令复制到终端请求一下,就可以得到渲染好的海报图片链接。 curl 命令中 Apikey 是临时的,可能会失效,你可以在 imgrender 中免费获取 API Key。 curl -X "POST" "https://api.imgrender.net/open/v1/pics" \ -H "Authorization: Apikey 183666749185461475.PLbfIpBpeMkpgbj1Tr+177Mv3Jo3wIIySyf8V5ZeDhs=" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{ "width": 640, "height": 1050, "backgroundColor": "#d75650", "blocks":[ { "x": 15, "y": 268, "width": 610, "height": 770, "backgroundColor": "#fff", "borderColor": "#fff" } ], "texts":[ { "x": 320, "y": 185, "text": "Davinci Li", "font": "jiangxizhuokai", "fontSize": 22, "color": "#fff", "width": 320, "textAlign": "center" }, { "x": 320, "y": 220, "text": "邀请你来参加抽奖", "font": "jiangxizhuokai", "fontSize": 22, "color": "#fff", "width": 320, "textAlign": "center" }, { "x": 30, "y": 640, "text": "奖品: 本田-CB650R 摩托车", "font": "jiangxizhuokai", "fontSize": 22, "color": "#000", "width": 580, "textAlign": "left" }, { "x": 30, "y": 676, "text": "01 月 31 日 18:00 自动开奖", "font": "jiangxizhuokai", "fontSize": 18, "color": "#9a9a9a", "width": 580, "textAlign": "left" }, { "x": 320, "y": 960, "text": "长按识别二维码,参与抽奖", "font": "jiangxizhuokai", "fontSize": 22, "color": "#9a9a9a", "width": 580, "textAlign": "center" } ], "lines":[ { "startX": 30, "startY": 696, "endX": 610, "endY": 696, "width": 1, "color": "#E1E1E1", "zIndex": 1 } ], "images":[ { "x": 248, "y": 25, "width": 120, "height": 120, "url": "https://img-chengxiaoli-1253325493.cos.ap-beijing.myqcloud.com/bikers_327390-13.jpg", "borderRadius": 60, "zIndex": 1 }, { "x": 108, "y": 285, "width": 400, "height": 300, "url": "https://img-chengxiaoli-1253325493.cos.ap-beijing.myqcloud.com/cb650R.jpeg", "zIndex": 1 } ], "qrcodes":[ { "x": 208, "y": 726, "size": 200, "content": "http://weixin.qq.com/r/yRzk-JbEbMsTrdKf90nb", "foregroundColor": "#000", "backgroundColor": "#fff", "zIndex": 1 } ] }' 请求返回内容: { "code":0, "message":"OK", "data":"https://davinci.imgrender.cn/c3037d467c163bd903760f96a34f3bcd.jpg?sign=1616722062-plQZQ4xtth9tEthx-0-2a98ba98e5fd44cc6dffb3aec6d3398f" } [代码]data[代码] 字段就是动态渲染好的海报图片链接,下载或打开链接就可保存图片。查看详细使用方法。 [图片] imgrender.net 推荐按以下最佳实践来使用海报生成服务: [图片] 所有的海报配置全都管理在服务端中,服务端只需要提供一个 API 给客户端。客户端通过这个 API 请求不同的分享图,服务端接收到请求后,先检查服务端是否缓存分享图。若没有缓存图片,则使用海报配置去 imgrender 动态生成海报,然后将生成的图片链接返回给客户端,供用户下载保存。 使用 imgrender 动态渲染海报,在满足需求的同时,可以大幅度降低开发成本,提高多端兼容性。无论是开发小程序海报,还是原生应用中的海报,一套代码即可搞定。 [图片] 原文链接:https://mp.weixin.qq.com/s/6ss1D4wneNDuhUfplualQQ 关键词:海报图、海报分享图、海报生成、图片生成、小程序海报生成
2023-11-11 - 小程序海报生成工具,可视化编辑直接生成代码使用,你的海报你自己做主
开门见山 工具地址 点我直达>>painter-custom-poster 由于挂载在github page上,打开速度会慢一些,请耐心等待或自行解决git网速问题 背景 在做小程序时候,我们经常会有一个需求,需要将小程序分享到朋友圈,但是朋友圈是不允许直接分享小程序,那我们还有其他的办法解决吗?答案肯定是有的,即 canvas 生成个性化海报分享图片到朋友圈 分析 小程序中有大量的生成图片需求,但是使用过 canvas 的人,都会发现一些难以预料的问题>>有关小程序的坑 直接在 canvas 上绘制图形,对于普通开发者来说代码会特别凌乱并且难以维护,经常会花费很久的时间去优化代码 不同的环境渲染问题,例如在开发者工具看起来好好的,一到 Android 真机,就出现图片不显示,位置不对应等等问题 解决 那可不可以开发一款生成海报的插件库呢? 首先,只需要提供一份简单的参数配置文件即可 解决掉小程序Canvas遇到的一些大大小小的坑 有严苛的测试环节,解决各种环境和各种机型遇到的问题,并提供稳定的线上版本 长期维护,并有专人更新迭代更新颖的功能 以上的要求当然是可以的,曾经的我也想尝试开发一款出来,但是后来尝试了几款现成的工具之后就放弃了,毕竟轮子这个东西,是需要不断维护更新的,另外已经有这么多优秀现成的插件了,我为何还要费力去写呢,贡献代码岂不更美哉,以下是我收集的几款 小程序生成图片库,轻松通过 json 方式绘制一张可以发到朋友圈的图片>>Painter 小程序组件-小程序海报组件>>wxa-plugin-canvas 微信小程序:一个 json 帮你完成分享朋友圈图片>>mp_canvas_drawer 我想干什么 唠了这么多,好像提供给大家插件就没我什么事情了…想走是不可能的 为了能够制作出更酷炫的海报,我思考了许久 虽然有了插件后,只需要提供配置代码就能够制作出一款海报来,但是我发现还是有些许问题 制作海报效率还是不够高,微调一个元素的大小和位置,就需要不断的修改保存代码,等待片刻,查看效果,真的烦 一个小小的位置调整可能就需要来回调整无数次,这种最简单的机械化劳动,这辈子是不可能的 拿着完美的稿子,递给设计师看,这个位置不对,这个线太粗,这个颜色太重…你信不信我打死你 对于一些精美复杂的海报,实现起来真的不太现实 那我需要怎么做呢,请点击这个链接体验>>painter-custom-poster 点击左侧例子展示中的任意一个例子,然后导入代码就能看到效果图,这下你应该能猜到了我的想法了 如何实现 刚开始我想用简单的html和css加拖动功能实现,通过简单尝试之后就放弃了,因为这个功能真的太复杂了,简单的工具肯定是不行的 中间这个计划停滞了很长时间,一度已经放弃 直到发现了这个库fabric.js,真的太太优秀了,赞美之词无以言表,唯一的缺点就是中文教程太少,必须生啃英文加谷歌翻译 fabric介绍,你可以很容易地创建任何一个简单的形状,复杂的形状,图像;将它们添加到画布中,并以任何你想要的方式进行修改:位置、尺寸、角度、颜色、笔画、不透明度等 How To Use 目前工具一共分成4部分 例子展示 用来将一些用户设计的精美海报显示出来,通过点击对应的例子并将代码导入画布中 画布区 显示真实的海报效果,画布里添加的元素,都可以直接用鼠标进行拖动,旋转,缩放操作 操作区 第一排四个按钮 复制代码 将画布的展示效果转化成小程序海报插件库所需要的json配置代码,目前我使用的是Painter库,默认会转化成这个插件的配置代码,将代码直接复制到card.js即可 查看代码 这个功能用不用无所谓,可以直观的看到生成的代码 导出json 将画布转化成fabric所需要的json代码,方便将自己设计的海报代码保存下来 导入json 将第3步导出的json代码导入,会在画布上显示已设计的海报样式 第二排五个按钮 画布 画布的属性参数 详解见下方 文字 添加文字的属性参数 详解见下方 矩形 添加矩形的属性参数 详解见下方 图片 添加图片的属性参数 详解见下方 二维码 添加二维码的属性参数 详解见下方 第三排 各种元素的详细设置参数 激活区 激活对象是指鼠标点击画布上的元素,该对象会被蓝色的边框覆盖,此时该对象被激活,可以执行拖动 旋转 缩放等操作 激活区只有对象被激活才会出来,用来设置激活对象的各种配置参数,修改value值后,实时更新当前激活对象的对应状态,点击其他区域,此模块将隐藏 快捷键 ‘←’ 左移一像素 ‘→’ 右移一像素 ‘↑’ 上移一像素 ‘↓’ 下移一像素 ‘ctrl + z’ 撤销 ‘ctrl + y’ 恢复 ‘delete’ 删除 ‘[’ 提高元素的层级 ‘]’ 降低元素的层级 布局属性 通用布局属性 属性 说明 默认 rotate 旋转,按照顺时针旋转的度数 0 width、height view 的宽度和高度 top、left 如 css 中为 absolute 布局时的作用 0 background 背景颜色 rgba(0,0,0,0) borderRadius 边框圆角 0 borderWidth 边框宽 0 borderColor 边框颜色 #000000 shadow 阴影 ‘’ shadow 可以同时修饰 image、rect、text 等 。在修饰 text 时则相当于 text-shadow;修饰 image 和 rect 时相当于 box-shadow 使用方法: [代码]shadow: 'h-shadow v-shadow blur color'; h-shadow: 必需。水平阴影的位置。允许负值。 v-shadow: 必需。垂直阴影的位置。允许负值。 blur: 必需。模糊的距离。 color: 必需。阴影的颜色。 举例: shadow:10 10 5 #888888 [代码] 渐变色支持 你可以在画布的 background 属性中使用以下方式实现 css 3 的渐变色,其中 radial-gradient 渐变的圆心为 中点,半径为最长边,目前不支持自己设置。 [代码]linear-gradient(-135deg, blue 0%, rgba(18, 52, 86, 1) 20%, #987 80%) radial-gradient(rgba(0, 0, 0, 0) 5%, #0ff 15%, #f0f 60%) [代码] !!!注意:颜色后面的百分比一定得写。 画布属性 属性 说明 默认 times 控制生成插件代码的宽度大小,比如画布宽100,times为2,生成的值为200 1 文字属性 属性名称 说明 默认值 text 字体内容 别跟我谈感情,谈感情伤钱 maxLines 最大行数 不限,根据 width 来 lineHeight 行高(上下两行文字baseline的距离) 1.3 fontSize 字体大小 30 color 字体颜色 #000000 fontWeight 字体粗细。仅支持 normal, bold normal textDecoration 文本修饰,支持none underline、 overline、 linethrough none textStyle fill: 填充样式,stroke:镂空样式 fill fontFamily 字体 sans-serif textAlign 文字的对齐方式,分为 left, center, right left 备注: fontFamily,工具中的第一个例子支持文字字体,但是导入小程序为什么看不到呢,小程序官网加载网络字体方法>> 加载字体教程>> 文字高度 是maxLines lineHeight2个字段一起计算出来的 图片属性 属性 说明 默认 url 图片路径 mode 图片裁剪、缩放的模式 aspectFill mode参数详解 scaleToFill 缩放图片到固定的宽高 aspectFill 图片裁剪显示对应的宽高 auto 自动填充 宽度全显示 高度自适应居中显示 Tips(一定要看哦~) 本工具不考虑兼容性,如发现不兼容请使用google浏览器 painter现在只支持这几种图形,所以暂不支持圆,线等 如果编辑过程,一个元素被挡住了,无法操作,请选择对象并通过[ ]快捷键提高降低元素的层级 文字暂不支持直接缩放操作,因为文字大小和元素高度不容易计算,可以通过修改激活栏目maxLines lineHeight fontSize值来动态改变元素 如发现导出的代码一个元素被另一个元素挡住了,请手动调整元素的位置,json数组中元素越往后层级显示就越高,由于painter没有提供层级参数,所以目前只能这样做 本工具导出代码全是以px为单位,为什么不支持rpx, 因为painter在rpx单位下,阴影和边框宽会出现大小计算问题,由于原例子没有提供px生成图片方案,可以下载我这里修改过的demo>>Painter即可解决 文本宽度随着字数不同而动态变化,想在文本后面加个图标根据文本区域长度布局, 请参考Painter文档这块教程直接修改源码 由于本工具开发有些许难度,如出现bug,建议或者使用上的问题,请提issue,源码地址>>painter-custom-poster 海报贡献 如果你设计的海报很好看,并且愿意开源贡献,可以贡献你的海报代码和缩略图,例子代码文件在example中,按顺序排列,例如现在库里例子是example2.js,那你添加example3.js和example3.jpg图片,事例可以参考一下文件夹中源码,然后在index.js中导出一下 导出代码 代码不要格式化,会报错,请原模原样复制到json字段里 生成缩略图 刚开始我想在此工具中直接生成图片,但是由于浏览器图片跨域问题导致报错失败 所以请去小程序中生成保存图片,图片质量设置0.2,并去tinypng压缩一下图片 找到painter.js,替换下边这个方法,可以生成0.2质量的图片,代码如下 [代码] saveImgToLocal() { const that = this; setTimeout(() => { wx.canvasToTempFilePath( { canvasId: 'k-canvas', fileType: 'jpg', quality: 0.2, success: function(res) { that.getImageInfo(res.tempFilePath); }, fail: function(error) { console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`); that.triggerEvent('imgErr', { error: error }); } }, this ); }, 300); } [代码] TODO 颜色值选择支持调色板工具 文字padding支持 缩放位置弹跳问题优化 假如需求大的话,支持其他几款插件库代码的生成 ~ 创作不易,如果对你有帮助,请给个星星 star✨✨ 谢谢 ~
2019-09-27 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20