最近公司项目使用小程序做序列帧动画,大概有 116 张图,共 7.4M。
比较闲的日子里实验了一番,主要有以下几种方法,
- css background-image + animation
- css background-position + animation
- js background-image
- js background-position
- js img src
- canvas drawImage
结果当然是 canvas 性能最优咯,不会出现掉帧和卡屏的情况,其中最不推荐第一种
所以这次项目也就准备尝试下微信小程序的 canvas 会不会有别样的风味。
基本上和 html 的 canvas 区别不大,方法名略有不同,
canvas.getContext('2d')
等于 wx.createCanvasContext(canvas)
。
再就是需要一个 draw()
方法才会绘制(经常容易忘记)。
至于 wx.createCanvasContext
放在 onReady
还是 onShow
,以及重复新建等问题上,由于项目紧凑,手里机型太少,没试太多
接着就开始了填坑之路:
一. Image 对象问题,只需直接使用图片路径
官方案例给的是 wx.chooseImage
返回的缓存文件,显然不是我们要的;
在 HTML 中如果想 drawImage
那就需要一个 Image
对象,需要先 new Image()
或者获取到 DOM 中的 <img>
,
那么小程序该怎么办呢,我略一沉凝,准备试它一试,直接使用了图片绝对路径,
ctx = ctx ? ctx : wx.createCanvasContext('imgs');
url = 'https://sum.kdcer.com/test/sw_shake/0/0 (1).jpg';
ctx.drawImage(url, 0, 0, 300, 500); // 直接使用图片路径
ctx.darw()
调试器上是正常的。url
为相对路径也是可以的。
当然,这个时候预加载就是个问题,只能在 wxml
中去 for
出所有的图片并 bindload
了。
2. 图片路径不能有特殊符号
上面的情况虽然调试器中可看,但手机预览时还是没有任何图片绘制上去呀(其他点线绘制是存在的),所以我怀疑仅仅是图片或小程序的问题。
然后去论坛博客寻找了番,不禁感叹资料可真少啊。
另一方面,我准备再试试 downloadFile 这种方法。
wx.downloadFile({
url: url,
success: function (res) {
ctx.drawImage(res.tempFilePath, 0, 0, 300, 500);
ctx.draw();
}
})
结果返回给我的 res.tempFilePath
是个 .htm
结尾的文件,报出 http 400
(请求无效)的错误。
我怀疑问题出在了文件本身,于是我改了下文件名,由 0 (1).jpg
改为 1.jpg
,就能正常访问了。
再后来进行了一些实验,暂时还只发现了 空格+括号 这一种命名会失败。
返回去再试一次, drawImage
直接使用命名正确的图片绝对路径其实是可以的,
只是还是那个小问题,加载得慢的话,图片异步没法绘制上去了,需要预加载。
所以更为推荐使用 downloadFile
这种方式来先加载图片再绘制。
比较坑的是,downloadFile
不能下载相对路径的图片,
这让我想优化把一部分图片放进小程序变得无比麻烦。
(其实 2M 资源放进去小程序就会变得非常卡,不推荐)
3. downloadFile 文件数限制
官方表示,downloadFile
最大并发限制为 10 个,
意味着直接 for
个 116 下是会报错的。
因此需要换用为递归的方式去预加载图片。
我写的递归不见得都适用,就不放出来了,应该没什么难点的。
(推荐先用 .html
写通递归,不然小程序编辑器死循环了很扎心)
4. downloadFile 合法域名的配置
开发完成后出现了小程序仅有打开了调试工具才能正常运行的情况,
后来经过同事点拨,原来还要设置 downloadFile
的合法域名,这个修改很简单。
回到调试器记得重启一次项目或者在预览页的配置 tab 中刷新一次。
每个月只能修改 5 次的限制应该不会造成什么影响。
5. requestAnimationFrame 问题
为了更好的动画优化,当然少不了 requestAnimationFrame
的存在。
然而,安卓机的小程序是有的,苹果机小程序却根本没有这个方法。
好在我们可以写段回退兼容:
if (typeof requestAnimationFrame == 'undefined') {
var lastTime = 0;
var requestAnimationFrame = function (callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = setTimeout(function () { callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (typeof cancelAnimationFrame == 'undefined') {
var cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
6. fps 性能问题
小程序一直吹嘘着接近原生的流畅体验,但这次帧动画的项目中显然打脸了。
html
版的 canvas
每 15ms 绘制一次都是小 case,
但小程序则需要 50ms 以上的间隔。否则会出现间断性白屏。
60fps 和 20fps 虽然在 html 中有很大差距,
但在小程序中 20fps 并没有太影响用户的浏览体验。
毕竟 js
的运算和 webview
的通信本身就不是多快的一件事,
而如果单单只考虑 webview
和 html
的话那当然有差距。
7. canvas 在小程序层叠上下文层级非常高
canvas
/ video
/ map
在小程序中的 z
轴层级非常高,甚至能盖过调试工具。
所以我们想在他们上面再叠一些元素就只能靠 cover-view
了。
但是,cover-view
只支持基本的定位、布局、文本样式,
不支持设置单边的 border
、opacity
、background-image
等。
我觉得不能叠图这个问题还是有些麻烦的,至少操作起来是这样。
而且,cover-view
暂不支持 css
动画。
事件不会冒泡到 canvas
及其父级,影响暂时感觉不大。
8. 小程序的 drawImage 不支持9个参数的传参
不过有人去改了编辑器源码,见 we_flappybird,
测了可行,但并不推荐,编辑器下次更新又要改。
总的来说,填坑的路是比较烦人的,
后一个问题解决了又开始想,是不是前一个问题其实本来是对的,然后又回去重来一遍,
最后的最后,来来回回,才能彻底填平这个坑。