备注:文章写的比较早,一直没发出来,里面有些内容可能和小程序最新的情况有一点点差异……
最近接触小程序开发,遇到了大量的坑,做个总结分(tu)享(cao)一下。
主要介绍坑,以及填坑的思路方向,具体实现细节略过。
小程序坑很多,网上已经有很多文章了,例如
小程序组件高度设置全屏
用CSS设置最外层组件属性height: 100%
时不能实现高度全屏,设置为height: 100vh
即可。
小程序加载图片填坑
加载图片有多种情况:
- 网络图片(http/https)
- 代码包文件(即小程序代码中的静态资源图片)
- 小程序云文件
- Base64编码的图片数据(js对象的数据类型为
String
) - 本地图片文件(即文件系统中的图片,例如从系统相册选择的,以及下载下来的临时文件等)
- 未经编码的原始图片数据(即Bitmap格式,js对象的数据类型为
ArrayBuffer
) - 编码过的图片数据(即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 | const base64 = wx.arrayBufferToBase64(data) |
备注:在网页环境下通常还可以把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组件加载。
未编码图片数据
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,又要通过奇怪的手法解决常规问题了。。。
可以使用vw
、vh
或rpx
给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环境下绘制出来的效果不一样,因为系统字体实现不一样。可以考虑加载固定的字体文件进行绘制。
- 环境配置复杂,可能需要自己搭建服务器、注册域名,以及繁琐的备案流程,要花钱。
- 存在一定的开发运维成本。
- 图片较大时消耗网络流量,建议编码压缩后再传输。
- 图片生成逻辑复杂时、用户量较大时消耗性能较多,建议考虑图片缓存,或者配置性能合适的服务器。
小程序网络请求域名限制
- 小程序默认有访问域名限制,需要在后台配置白名单。
- 开发环境可以配置跳过校验。
- 小程序必须发起HTTPS请求。
详见官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html
绕过小程序网络限制
小程序网络限制主要应该是出于安全性的考虑。有没有办法绕过小程序的网络请求限制呢?
办法还是有的,通过小程序云函数转发网络请求即可。云函数转发的方式直接利用小程序提供的网络通道,可以做到间接访问任意域名、直接用HTTP以及IP地址、任意端口访问服务器,不需要配置域名白名单,还能保证服务器的安全性(不能通过手机抓包获取真实的服务器)。
例如下面的云函数利用axios实现了一个简单的文件下载中转功能(由于云函数似乎不支持返回数据流,因此直接一次性返回了文件完整数据,不宜传输过大文件)。
在小程序端需要下载任意域名下的一张图片时,直接调用该云函数,并将url作为参数传递,即可返回图片的数据,从而进行显示或处理。
1 | const cloud = require("wx-server-sdk"); |
获取小程序码接口填坑
官方提供了获取小程序码的接口
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html
-
对于只需要生成固定小程序码的情况,建议提前生成好,用的时候直接用现成图片即可;对于需要在小程序码中动态添加附加字段信息的情况,才需要调用接口。
-
出于安全考虑,接口所在的域名
api.weixin.qq.com
不能直接在小程序中调用(因为请求中包含了AppID、Secret、Token等加密参数),也不能在后台配置域名白名单,必须通过后台发起(云函数或者服务器),再传给小程序。 -
接口请求失败时会返回Json数据,请求成功后会直接返回图片的二进制数据。用网络库发起请求时必须指定编码,否则二进制数据默认会被转成string,这个过程是有损、不可逆的,string再转成二进制数据就和原始数据不一样了,导致保存的图片根本打不开。具体来说就是:
1 | // 如果使用request库 |
小程序云函数填坑
小程序云函数主机上获取的时间是UTC时区,服务端处理时间时一定要注意时区问题。