最近作微信小程序的开发时,想作一个靠感知手机方向,使页面上节点跟随移动的动画(即重力感应视差效果)功能。结果发现微信小程序有一些坑:javascript
微信小程序不支持html5的DeviceOrientationEvent重力感应API,而是本身实现的wx.onAccelerometerChangehtml
这个API回调实现,频率为5次/shtml5
在这个背景下,要实现平滑的重力感应的视差体验就那么优雅了,由于人对至少60帧每秒的动画才会感受流畅。最终实现的效果会有卡顿现象。java
实现期间,想起好像有requestAnimationFrame
这个跟动画相关的API,其功能表现与setTimeout
相似,即隔一段时间调用一个回调函数。对于这个API,以前了解不深,此次拿起来产生了一个疑问:既生setTimeout
,何生requestAnimationFrame
?带着疑问,开始调研。web
在MDN上,关于requestAnimationFrame
的定义是:chrome
window.requestAnimationFrame()这个方法是用来在页面重绘以前,通知浏览器调用一个指定的函数,以知足开发者操做动画的需求。这个方法接受一个函数为参,该函数会在重绘前调用。小程序
在这里,我产生一个疑问,所谓的“在页面重绘以前”,指的是,这个指定函数(如下简称cb)会在底层机制的运行下,在页面重绘以前调用;仍是人为地设置一个间隔时间,去调用cb,致使重绘?微信小程序
以前大概了解过页面的重排和重绘。动画能用重排和重绘来实现(这里指的是能用这两种途径来达到动画目的,而不是二者都适合用来实现动画),而定义里只提到重绘,没提到重排的缘由,虽然我没去细究,但很重要一点确定是由于,重绘性能远高于重排。因此动画不要经过left、margin等来实现,应该经过translate属性来实现。api
既然说到translate,稍微延伸一下,动画若是要用translate,最好用tranlate3d。由于较于tranlate,tranlate3d能获得更完整的GPU加速的支持,使得性能更优。浏览器
言归正传,继续解决刚刚的疑问。往下阅读,发现这么一段解释:
若是你想作逐帧动画的时候,你应该用这个方法。这就要求你的动画函数执行会先于浏览器重绘动做。一般来讲,被调用的频率是每秒60次,可是通常会遵循W3C标准规定的频率。若是是后台标签页面,重绘频率则会大大下降。
从这段话能够看出,在用了这个方法后,浏览器会根据本身的重绘频率,而每次重绘前会调用cb。用如下代码验证:
var laststart function test () { laststart && console.log(Date.now() - laststart) laststart = Date.now() requestAnimationFrame(test) } requestAnimationFrame(test)
获得结果,我所在的浏览器环境(mac + chrome[55.0.2883.95])的重绘频率约为60次每秒:
对此我产生几个疑问:
问题1:若是我干扰了重绘的频率,是否还会是一个几乎保持在每秒60帧的频率呢(只针对提升频率进行探究)?
问题2:是否调用了requestAnimationFrame就会产生必定频率的重绘?
问题3:若是不调用requestAnimationFrame,在无其余代码去重绘页面的话,页面就不会重绘吗?
为了验证问题1,我加了这么一段代码:
var x = 1 var style = document.querySelectorAll('.test')[0].style function interference () { x *= -1 style.transform = `translate3d(${x * 20}px, 0 ,0)` setTimeout(interference, 5) } interference()
获得的结果与上一个结果一致。
由此得出结论:cb的调用频率在人为干扰重绘频率的状况下,依旧我行我素。
等等,人为干扰重绘频率成功了吗?会不会虽然interference
的调用频率为5ms一次,但浏览器的重绘频率依旧是约等于60次每秒,即interference
虽然试图去触发浏览器5ms重绘一次,但浏览器只会阻塞住,等下一次浏览器默认频率重绘时再一块儿重绘?
为了探究这个问题,我将interference
的setTimeout时间分别设置为16ms(简称为i16)、10ms(简称为i10)、8ms(简称为i8)、5ms(简称为i5),若是浏览器重绘频率没法人为干扰,因如下两个缘由:
interference
的函数对$('.test')
的改变为水平位移正负20px交替出现
浏览器默认重绘频率接近16ms一次
i8会因16ms中被调用2次,使得$('.test')
回归原位而致使肉眼看到的$('.test')
闪动频率最慢;而i16的调用频率和重绘频率最为接近,在这种状况下,肉眼看到的$('.test')
闪动频率会是最快。
结果肉眼看到的闪动频率从高到低依次是:i16 > i十、i5 > i8
。
故得出结论,用上面的方法,没法人为干扰浏览器默认的重绘频率。
那么是否有办法设置浏览器的重绘频率呢?没有查到直接答案,但在阮一峰的网页性能管理详解中,有提到:
大多数显示器的刷新频率是60Hz,为了与系统一致,以及节省电力,浏览器会自动按照这个频率,刷新动画
证实浏览器的重绘频率和显示器的刷新频率相等。因此应该没有直接设置浏览器重绘频率的方法,毕竟页面重绘了,但显示器没刷新,影响了性能却没效果。
其实经过chrome自带的开发者工具里的timeline功能,就能清晰看到,上面的方法的间隔时间不管怎么设置,都不会改变重绘频率:
有了这个工具,其他两个问题也迎刃而解:
问题2的答案:调用了requestAnimationFrame就会产生必定频率的重绘,只是这种状况下的重绘会因并没有实质重绘内容,而历时极短。
问题3的答案:若是不调用requestAnimationFrame,在无其余代码去重绘页面的话,页面就不会重绘
调研到这一步,发现requestAnimationFrame和setTimeout根本不是一回事。requestAnimationFrame是一个根据浏览器重绘频率来调用的方法,setTimeout则是一个计时器。定义不一样,适用的场景也彻底不一样,也没有性能高低之分。
MDN给出的requestAnimationFrame的兼容性以下:
也就是说requestAnimationFrame确定有兼容性问题。因此降级处理也是必须的。如下是降级代码:
;(function() { var lastTime = 0; // 兼容各类浏览器 var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } // 降级处理 if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback, element) { // 保证若是重复执行callback的话,callback的执行起始时间相隔16ms var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } }());
微信iOS版小程序彻底不支持requestAnimationFrame
requestAnimationFrame和setTimeout根本不是一回事,根据其定义,能够在不一样场景下使用。
较于tranlate,tranlate3d能获得更完整的GPU加速的支持。
浏览器对页面的重绘有一个默认的最大频率,最大频率没法人为设置,也没有设置的必要。
调用了requestAnimationFrame就会产生必定频率的重绘,只是这种状况下的重绘会因并没有实质重绘内容,而历时极短。
若是不调用requestAnimationFrame,在无其余代码去重绘页面的话,页面就不会重绘。
最后,附上咱们趣店集团的小程序二维码: