微信小程序开发填坑指南

备注:文章写的比较早,一直没发出来,里面有些内容可能和小程序最新的情况有一点点差异……

最近接触小程序开发,遇到了大量的坑,做个总结分(tu)享(cao)一下。

主要介绍坑,以及填坑的思路方向,具体实现细节略过。

小程序坑很多,网上已经有很多文章了,例如

https://www.google.com/search?q=小程序+坑

小程序组件高度设置全屏

用CSS设置最外层组件属性height: 100%时不能实现高度全屏,设置为height: 100vh即可。

小程序加载图片填坑

加载图片有多种情况:

  1. 网络图片(http/https)
  2. 代码包文件(即小程序代码中的静态资源图片)
  3. 小程序云文件
  4. Base64编码的图片数据(js对象的数据类型为String
  5. 本地图片文件(即文件系统中的图片,例如从系统相册选择的,以及下载下来的临时文件等)
  6. 未经编码的原始图片数据(即Bitmap格式,js对象的数据类型为ArrayBuffer
  7. 编码过的图片数据(即PNG、JPG等格式,js对象的数据类型为ArrayBuffer

网络图片、代码包图片、小程序云文件

这几种都比较简单,直接用image组件的src属性即可。网络请求限制必须https而不能是http,图片目前不限制。

1
<image src="/image/arrowright.png" mode="aspectFill"></image>

image组件参考 https://developers.weixin.qq.com/miniprogram/dev/component/image.html

代码包文件可参考 https://developers.weixin.qq.com/miniprogram/dev/framework/ability/file-system.html

Base64图片

直接设置给image组件即可

1
<image src="data:image/png;base64,图片base64数据" mode="aspectFill"></image>

本地文件

小程序的image组件src属性设置为本地图片发现不能显示。

1、如果只是全屏查看图片,或者简易的保存图片、分享朋友圈功能,直接使用小程序自带的图片预览wx.previewImage即可。

https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.previewImage.html

2、绘制到Canvas上。可以用CanvasContext.drawImage绘制,参数为图片路径。

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.drawImage.html

3、读取文件数据并转成Base64显示。以png格式图片示例代码如下。

1
<image src="{{image}}"></image>
1
2
const base64 = wx.arrayBufferToBase64(data)
this.image = 'data:image/png;base64,' + base64

备注:在网页环境下通常还可以把arraybuffer转成Blob格式显示,但是由于小程序目前不支持Blob,所以只能用Base64格式了。可参考 https://github.com/abbshr/abbshr.github.io/issues/28

4、上传到服务器转换成网络图片,再用image组件加载。可以用wx.uploadFile或者wx.request网络请求上传。

https://developers.weixin.qq.com/miniprogram/dev/api/network/upload/wx.uploadFile.html

https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html

5、用wx.cloud.uploadFile上传到小程序云文件,再用image组件加载。

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-client-api/storage/uploadFile.html

未编码图片数据

1、图片数据转换成Base64显示。

2、绘制到Canvas上。可以用wx.canvasPutImageData绘制。

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasPutImageData.html

已编码图片数据

1、转成Base64显示。

2、写入到临时文件,然后绘制到Canvas上。

3、上传到服务器再用image的src属性加载。

小程序Canvas填坑指南

HTML中Canvas的API文档 https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API

小程序封装的CanvasContext对应的是HTML中的CanvasRenderingContext2D对象。

Canvas的一些坑总结

  • 需要注意,不少API和HTML中的Canvas不一样,例如HTML中fillStyle等属性在小程序中都是方法,align和baseline支持的属性值也不一样等等。

  • 在小程序中js不能获取DOM树,因此也不能像网页一样随意操作Canvas节点。

  • Canvas是小程序原生组件,有很多注意事项,例如会始终覆盖在其他非原生组件上方等

    原生组件注意事项参考 https://developers.weixin.qq.com/miniprogram/dev/component/native-component.html

  • 兼容性不好,有BUG,在Android和iOS上的表现不一致,例如在iOS设备上调用了Canvas.rotate方法可能会导致之后所有的绘制指令失效。被坑惨了,填了很久Canvas的坑,写了一堆代码,在Android上调试一切正常,发布了才发现iOS上完全不能用,最终还是不得不放弃了用Canva生成图片的方案,转而去踩其他坑。。。

Canvas尺寸的获取和适配

小程序似乎没有提供Canvas获取尺寸的API,又要通过奇怪的手法解决常规问题了。。。

可以使用vwvhrpx给Canvas设置尺寸,然后通过wx.getSystemInfo获取窗口尺寸,从而计算出Canvas的真实尺寸。

换算关系:

  • 窗口宽度 = 100vw
  • 窗口高度 = 100vh
  • 屏幕宽度 = 750rpx

https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html

https://developers.weixin.qq.com/miniprogram/dev/api/system/system-info/wx.getSystemInfo.html

隐藏Canvas

有时需要在Canvas不可见的情况下生成图片,但是Canvas是原生组件,始终在其他元素上方,而设置display:none并不会隐藏。此时可以通过CSS设置 left: 10000rpx将其移到屏幕外,或者在Canvas外面嵌套一个元素并设置style="width:0;height:0;overflow:hidden;",从而实现效果。

动态添加/移除Canvas

可以在Canvas标签外包裹一个view标签,然后用wx:if="{{condition}}控制。

https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/conditional.html

Canvas文字排版

Canvas本身不支持文字排版(自动换行等)。

实现水平文字自动换行比较简单,循环遍历所有字符调用measure判断,然后拆分成多行即可。

竖向文字排版相对复杂一些,英文竖向排版文字会旋转,而中文文字不旋转。Canvas的旋转目前有坑,建议放弃……

小程序图片生成填坑

图片生成是一个很麻烦的问题,坑非常多。我做的一个简单的小程序本来两天搞定了,然而由于需要垂直文字排版,用Canvas生成的图片用到rotate,发现在iOS上没法用(前面已经说了),之后换了好几种方案,花了一周才终于解决问题。。。

图片生成有多种方案,下面一一总结。

本地使用Canvas绘制

好处:

  • 不需要处理服务端的问题,更不需要自己搭建服务器、注册域名、备案等

缺点:

  • 小程序封装的Canvas坑非常多。。。
  • 客户端兼容问题,例如屏幕尺寸不一致等
  • 客户端性能问题,复杂操作可能会导致内存不足崩溃,或者耗时很久

利用图床水印功能实现

一些简单的图片合成任务,例如在图片上添加头像、加几个简单的单行文字,可以直接利用七牛云图床的水印功能实现。

详情可参考七牛云的文档

https://developer.qiniu.com/dora/manual/1316/image-watermarking-processing-watermark

优点:

  • 使用简单,七牛云还支持可视化界面获取水印参数,可以支持多个水印操作
  • 成本低,不需要开发,不用担心客户端兼容、性能问题,也不用担心服务端计算性能和网络性能问题

缺点:

  • 功能比较单一,稍微复杂一点的需求实现不了
  • 图片固定链接需要绑定七牛云提供的子域名并备案
  • 免费版本流量有限,流量超了就需要收费

服务端生成

服务端生成图片有很多办法,例如用Java实现。这里主要总结Node环境下的开源库。

Sharp

https://github.com/lovell/sharp

  • 性能比较好,支持的文件格式多。
  • 不支持文本绘制,需要结合 text-to-svg 将文本转成SVG格式的图片,然后用sharp合成。
  • sharp要通过图层合成图片和文本,每个元素都是一个独立的图层,每次只能合成两个图层。因此如果需要绘制很多元素,代码实现起来会比较繁琐,而且不知道反复合并图层会不会影响性能。
  • text-to-svg只支持简单的单行文本转图片,文本换行需要自行实现。而如果是竖排文本这种需求,可能得每个字符生成单独的SVG,再用sharp合成,猜测可能会有性能问题。。。

sharp图片库用法总结 http://www.paincker.com/node-sharp

Jimp

https://github.com/oliver-moran/jimp

相比sharp,Jimp支持的图片格式少一点,但是Jimp原生支持添加文本,没有详细研究。

NodeCanvas

https://github.com/Automattic/node-canvas

前面两种图片库都是从图片处理的角度来对图片进行裁剪、缩放、调整、图层合成等操作,而HTML中Canvas的思路则是在画布上绘制,思路不太一样,对于复杂元素的合并,以及复杂的文字排版,用Canvas会更容易。

NodeCanvas是一个可以在后台Node环境下操作Canvas生成图片的库,用起来很方便,几乎实现了HTML中Canvas的所有属性,因此也可以直接复用前端Canvas绘制代码。绘制完成后可以输出文件、数据流等,还可以指定图片的编码格式和压缩比。

Canvas的API文档 https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API

缺点:NodeCanvas的环境配置问题会多一点。前面两种库都是直接npm install就安装好了,因此甚至可以直接在小程序提供的云函数环境中使用,而不需要自己搭建服务器。而我实际开发,发现NodeCanvas在小程序云环境果然安装失败了,原因没有深入研究,最后干脆改成了自己搭建服务器。

总结

服务端生成图片的优缺点

优点:

  • 不用担心客户端兼容性和性能问题
  • 不再受限于小程序提供的坑爹Canvas API,可以使用任何熟悉的Web技术、语言和开源库实现
  • 灵活性最高,只要能写代码,可以实现很复杂的效果,一种图片库解决不了问题换另一种,实在不行自己实现底层图片操作逻辑。

缺点:

  • 需要注意,后台生成图片如果需要绘制文本,可能会有字体的问题。例如Linux可能默认不支持中文,导致画出来的中文是乱码,再例如同样的代码同样的字体,在Mac、Windows、Linux环境下绘制出来的效果不一样,因为系统字体实现不一样。可以考虑加载固定的字体文件进行绘制。
  • 环境配置复杂,可能需要自己搭建服务器、注册域名,以及繁琐的备案流程,要花钱。
  • 存在一定的开发运维成本。
  • 图片较大时消耗网络流量,建议编码压缩后再传输。
  • 图片生成逻辑复杂时、用户量较大时消耗性能较多,建议考虑图片缓存,或者配置性能合适的服务器。

小程序网络请求域名限制

  1. 小程序默认有访问域名限制,需要在后台配置白名单。
  2. 开发环境可以配置跳过校验。
  3. 小程序必须发起HTTPS请求。

详见官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html

绕过小程序网络限制

小程序网络限制主要应该是出于安全性的考虑。有没有办法绕过小程序的网络请求限制呢?

办法还是有的,通过小程序云函数转发网络请求即可。云函数转发的方式直接利用小程序提供的网络通道,可以做到间接访问任意域名、直接用HTTP以及IP地址、任意端口访问服务器,不需要配置域名白名单,还能保证服务器的安全性(不能通过手机抓包获取真实的服务器)。

例如下面的云函数利用axios实现了一个简单的文件下载中转功能(由于云函数似乎不支持返回数据流,因此直接一次性返回了文件完整数据,不宜传输过大文件)。

在小程序端需要下载任意域名下的一张图片时,直接调用该云函数,并将url作为参数传递,即可返回图片的数据,从而进行显示或处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const cloud = require("wx-server-sdk");
const axios = require("axios");

cloud.init();

exports.main = async (event, context) => {
if (event.url) {
return (await axios({
url: event.url,
method: "get",
responseType: "arraybuffer"
})).data;
} else {
return Promise.reject("file download require param 'url'");
}
};

获取小程序码接口填坑

官方提供了获取小程序码的接口

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html

  1. 对于只需要生成固定小程序码的情况,建议提前生成好,用的时候直接用现成图片即可;对于需要在小程序码中动态添加附加字段信息的情况,才需要调用接口。

  2. 出于安全考虑,接口所在的域名 api.weixin.qq.com 不能直接在小程序中调用(因为请求中包含了AppID、Secret、Token等加密参数),也不能在后台配置域名白名单,必须通过后台发起(云函数或者服务器),再传给小程序。

  3. 接口请求失败时会返回Json数据,请求成功后会直接返回图片的二进制数据。用网络库发起请求时必须指定编码,否则二进制数据默认会被转成string,这个过程是有损、不可逆的,string再转成二进制数据就和原始数据不一样了,导致保存的图片根本打不开。具体来说就是:

1
2
3
4
5
6
7
8
9
10
11
// 如果使用request库
request.get({
url: url,
encoding: null // 指定编码
});
// 如果使用axios库
axios({
url: url,
method: "get",
responseType: "arraybuffer" // 指定编码
})

参考文章 https://segmentfault.com/a/1190000002787763

小程序云函数填坑

小程序云函数主机上获取的时间是UTC时区,服务端处理时间时一定要注意时区问题。