前言
活动海报制作,在 C 端是很常见的功能,前端程序员应该或多或少的接触过,h5 写的话方案成熟,可以用原生 canvas api 写,也可以使用成熟的插件如 html-to-image、html2canvas 等; 不过在微信小程序中开发这个功能,第一次的话就会遇到挺多坑的,api 的兼容性、凌乱的文档都在暗示我们过程没那么简单;
如果你也在做相关的功能,这篇博客可能会有所帮助!
大致效果预览:
方案评估
基本功能
在当前活动中点击按钮可生成一张活动海报,可保存到相册或者直接微信转发,海报中的小程序码和分享人有映射关系,可以记录谁分享的。
两类方案
这种需求在营销活动中其实很常见,常见的方案有两类:
一是后端生成
、前端展示,好处是兼容性良好,不需要关注设备和平台之间的差异性;不过稍微复杂点的UI, 后端的工作量剧增,画起来太费劲。
二是前端生成
,后端只提供关键数据,由前端通过 canvas 等技术来制作图片,具体又分纯 canvas 绘制和插件编译 Dom 等方案如(html2canvas)。
具体采用哪种方案也要看具体的情况了,网上也有不少讨论,针对小程序环境,html2canvas 是不支持的,所以如果要使用插件,还需要额外的工作,比如将代码运行在 webview 中,最后我还是决定使用 小程序提供的 canvas api 来绘制海报。
思维导图
值得分享的技术点解析
初始化 canvas 环境
我们使用最新type="2d"模式,性能和写法上更接近原生
<canvas type="2d" id="canvas2d" class="canvasTag"></canvas>
注意:官方文档例子是写在小程序页面里的,模式为 page, 如果是写在小程序组件里 Component, 需要使用 in(this)
来固定 this 对象, 否则 res 获取不到。
示例: const query = wx.createSelectorQuery().in(this) query.select('#canvas2d') .fields({ node: true, size: true }) .exec(async res => { const canvas = res[ 0 ].node
canvas 尺寸和生成图片的关系
因为canvas 是以像素来绘制的,不像 svg 那样可以无损,所以为了在高分屏设备上保持清晰度,需要根据当前设备的 dpr 来对canvas 尺寸进行放大,保持比例,这样可以消除图片的锯齿感。
示例: const canvas = res[ 0 ].node canvas.width = windowWidth * dpr canvas.height = windowHeight * dpr const ctx = canvas.getContext('2d') ctx.scale(dpr, dpr)
文字的换行计算
canvas 绘制不像 DOM 可以自适应位置,所以需要根据文字数量动态计算行数和占用到高度
formatText (context, text = '' , x, y, maxWidth, lineHeight) { let arrText = text.split('') let line = '' let row = 1 for (let n = 0; n < arrText.length; n++) { let testLine = line + arrText[n] let metrics = context.measureText(testLine) let testWidth = metrics.width if (testWidth > maxWidth && n > 0) { // 超出 2 行缩略,显示 ... if (row === 2) { for (let n = 0; n < line.length; n++) { // eslint-disable-next-line no-undef, no-new-wrappers let $testLine = new String(line).substring(0, line.length - 1 - n) + '...' let $metrics = context.measureText($testLine) let $testWidth = $metrics.width if ($testWidth <= maxWidth) { context.fillText($testLine, x, y) return } } } else { context.fillText(line, x, y) } line = arrText[n] y += lineHeight row++ } else { line = testLine } } context.fillText(line, x, y) }
获取指定文本的高度
// 获取文本高度 getFormatTextHeight (context, text = '', maxWidth, lineHeight) { let arrText = text.split('') let line = '' let row = 1 for (let n = 0; n < arrText.length; n++) { if (row === 2) { break } let testLine = line + arrText[n] let metrics = context.measureText(testLine) let testWidth = metrics.width if (testWidth > maxWidth && n > 0) { line = arrText[n] row++ } else { line = testLine } } return row * lineHeight }
Base64 图片的展示
Base64 类型的图片不能直接渲染到画布上,需要先缓存下来转为本地路径, 主要用到小程序提供的 wx.getFileSystemManager() 和 writeFile 两个 api。
// base64 转本地文件 setBase64Save (base64File) { const fsm = wx.getFileSystemManager() // eslint-disable-next-line no-useless-escape let extName = base64File.match(/data:\S+/(\S+);/) if (extName) { extName = extName[1] } let fileName = Date.now() + '.' + extName return new Promise((resolve, reject) => { let filePath = wx.env.USER_DATA_PATH + '/' + fileName fsm.writeFile({ filePath, data: base64File.replace(/^data:\S+/\S+;base64,/, ''), encoding: 'base64', success: res => { resolve(filePath) }, fail () { // eslint-disable-next-line prefer-promise-reject-errors reject('写入失败') } }) }) }
网络图片的绘制
社区说网络图片不能直接绘制,需要转化为本地路径 path(但是当这样操作后,发现真机调试时候并没有成功,反而使用直接绘制(drawImage)的方式成功了。)
wx.getImageInfo({ src: headerImg, success (res) { headerImgObj.src = res.path || '' headerImgObj.onload = resolve headerImgObj.onerror = reject } })
版本兼容
实测发现,开发者工具中的内核版本和真机并不完全一致,所以建议要在真机上调试下,否则可能在上线后出现不同的兼容性问题;比如 wx.drawImage 这个 api 实测在基础库版本 2.25.0 之上才能正常使用,可以在微信管理后台中配置最低使用版本 2.25.2,可自动给低版本用户提示更新微信以使用当前小程序。
设计稿的建议
建议以标准尺寸设计稿如 750px 来绘制画布,然后通过缩放来展示。
总结
小程序的 canvas 标签和原生的有较大差异,需要结合文档和社区反馈,多做调试,api 经常变动也需要关注,如果某个接口废弃了,要注意在下个迭代发布时做好处理,因为只要发布,之前的接口就失效了;
以上就是我在做小程序海报时候觉得有价值、值得分享的点,希望对大家有帮助。