背景

之前做了个项目类似于“水印相机“,需求是在调用相机时增加水印模板,水印有不同的样式和类型,拍照后也在照片上打上相同的水印,可以参考下图:
image.png
老板的需求是在微信小程序上实现,本想直接用canvas搞定,mvp情况下不考虑复用情况前端写死几个模板,需要迭代的时候,copy一下改改参数重新上线就是了

可惜强烈要求做到后台配置化(预览/编辑/新建/删除/…),且不需要研发人员进入就可以完成日常迭代,那就只好考虑换一套方案了

需求关键点

  • 设计直接输出程序可用的模板数据
  • 后台配置模板上线
  • 小程序内可使用(获取数据)

方案描述

  • 恰好之前有跑过imgcook(什么是imgcook?)的demo,可以通过sketch直接导出wxml到imgcook,让非技术同学也可以通过可视化导出程序可用的数据
  • 拿到wxml之后在后台管理界面拉一个页面出来作后台配置,确认后模板字符串到数据库
  • 小程序端解析编辑后的模板字符串(wxml),将其转换为canvas代码实现动态展示,需要魔改下wxml-to-canvas插件

这套方案基本上满足了配置化的要求,可以让运营/设计同学直接发布模板而不需要占用研发资源,实现成本相对来说比较低,且都可作为插件形式,不影响原先逻辑

编写模板

设计稿转imgcook可用文件

设计常用的一些软件(ps/sketch/figma/….),其实本身是有自身的scheme形式,通过scheme可以导出自己需要的数据形态,像imgcook就做了一个sketch的插件

通过插件可以直接导出imgcook可编辑的数据形态,当然为了效果更好设计需要遵循它的一些规范(后续为了方便就直接用imgcook原生的编辑器进行演示)

由静态文件转动态模板

因为设计给出的图是一个静态图,但我们需要的是一个文本可替换,甚至有一些交互逻辑的动态模板,所以需要按照一定的规范进行二次编辑

文本替换

image.png
模板的用户昵称,及相关属性需要根据后台返回动态生成,可以通过imgcook的编辑器能力将其替换为表达式
image.png

if判断

并不是模板的所有项都必须显示,比如”天气“可能是个可选项,不需要展示的时候可以同样通过imgcook的编辑器能力将其替换为表达式即可,当然变量命名为了后续解析方便可以约定一些格式标识是属于if类型
image.png
我这里的话会在后面加一个驼峰的Visible,标识该文本需要控制是否显示

扩展

其他能力暂不在此赘述了,可以参考vue模板的实现

获取wxml

编辑完成后点击导出即可获得可用的文本,我这边直接因为是给小程序用直接导出的wxml,至此imgcook的任务就结束了
image.png

录入数据库

这里附上一个简单录入模板界面,大致流程: 上方input将导出的模板字符串复制进来后,自动识别出”{{}}“表达式和”wx:if“关键字,并在水印字段编辑中进行展示(字段编辑和模板字符串为双向绑定)
image.png
进行再次调整,并输入一些默认值之后即可选择保存录入数据库

至于预览,可以直接用html-to-canvas转一道就好了

模板展示

此时已经可以从接口拿到模板字符串,但要注意这只是一堆字符串,并非结构化数据,要进行渲染的话需要将模板内的占位符全部替换为有效数据

解析模板字符串

可以直接用正则匹配的方式进行解析,不过从0手撸一个实在是太耗时了,直接使用htmlParser将节点转为json,获取关键字和占位符,替换为服务端 + 客户端本地(经纬度/陀螺仪/…)数据

因为后续canvas渲染需要用wxml和style,这里需要将所有元素的内联css抽出组成一个单独json,最后再将json再转回wxml

  1. traversalHtml(html, data) {
  2. // 解决转义问题
  3. html = html.replace(/[\b\f\r\t\\]/g, '');
  4. // 返回结果
  5. return new Html2Canvas(html, data).traversalHtml();
  6. }

canvas渲染

web端有html-to-canvas,小程序端也有wxml-to-canvas,将上面转换后的wxml模板和style json对象丢进去即可正常渲染

  1. drawTemplate(templateParams) {
  2. this.widget = this.selectComponent('.widget');
  3. const p1 = this.widget
  4. .renderToCanvas({
  5. wxml: templateParams.wxml,
  6. style: templateParams.style,
  7. })
  8. .then(res => {
  9. // 绘制完成
  10. this.drawOver(res);
  11. });
  12. }

总结

本文主要是记录一次之前的思路,很多东西没写很细,如果有这方面需求可以详细看下文中用到的几个技术栈和js库
本身没什么难度,主要是模板的抽取和转换比较麻烦

可以展望下,如果用canvas直接写一些简单的页面页面的话,是不是可以也可以实现小程序的热更新?