10.1.2 脚本方式
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
10.1.2 脚本方式
相对于CSS3方式,脚本方式最大的好处就是更强的灵活度,开发者可以任意控制动画的时间长度,也可以控制每个时间点上元素渲染出来的样式,可以更容易做出丰富的动画效果。
脚本方式的缺点也很明显,动画过程通过JavaScript实现,不是浏览器原生支持,消耗的计算资源更多。如果处理不当,动画可能会出现卡顿滞后现象,本来使用动画是为了创造更好的用户体验,如果出现卡顿,反而对用户体验带来不好的影响。
最原始的脚本方式就是利用setInterval或者setTimeout来实现,每隔一段时间一个指定的函数被执行来修改界面的内容或者样式,从而达到动画的效果。
在本书的Github代码库https://github.com/mocheng/react-and-redux中的文件chapter-10/animation_types/setInterval_animation.html文件中可以找到一个例子,JavaScript部分代码如下:
var animatedElement = document.getElementById("sample");
var left = 0;
var timer;
var ANIMATION_INTERVAL = 16;
timer = setInterval(function() {
left += 10;
animatedElement.style.left = left + "px";
if ( left >= 400 ) {
clearInterval(timer);
}
}, ANIMATION_INTERVAL);
在上面的例子中,有一个常量ANIMATION_INTERVAL定义为16,setInterval以这个常量为间隔,每16毫秒计算一次sample元素的left值,每次都根据时间推移按比例增加left的值,直到left大于400。
为什么要选择16毫秒呢?因为每秒渲染60帧(也叫60fps,60Frame Per Second)会给用户带来足够流畅的视觉体验,一秒钟有1000毫秒,1000□60≈16,也就是说,如果我们做到每16毫秒去渲染一次画面,就能够达到比较流畅的动画效果。
对于简单的动画,setInterval方式勉强能够及格,但是对于稍微复杂一些的动画,脚本方式就顶不住了,比如渲染一帧要花去超过32毫秒的时间,那么还用16毫秒一个间隔的方式肯定不行。实际上,因为一帧渲染要占用网页线程32毫秒,会导致setInterval根本无法以16毫秒间隔调用渲染函数,这就产生了明显的动画滞后感,原本一秒钟完成的动画现在要花两秒钟完成,所以这种原始的setInterval方式是肯定不适合复杂的动画的。
出现上面问题的本质原因是setInterval和setTimeout并不能保证在指定时间间隔或者延迟的情况下准时调用指定函数。所以可以换一个思路,当指定函数调用的时候,根据逝去的时间计算当前这一帧应该显示成什么样子,这样即使因为浏览器渲染主线程忙碌导致一帧渲染时间超过16毫秒,在后续帧渲染时至少内容不会因此滞后,即使达不倒60fps的效果,也能保证动画在指定时间内完成。
下面是一个这种方法实现动画的例子,首先我们实现一个raf函数,raf是request animation frame的缩写,代码如下:
var lastTimeStamp = new Date().getTime();
function raf(fn) {
var currTimeStamp = new Date().getTime();
var delay = Math.max(0, 16 - (currTimeStamp - lastTimeStamp));
var handle = setTimeout(function(){
fn(currTimeStamp);
}, delay);
lastTimeStamp = currTimeSt&
return handle;
}
在上面定义的raf中,接受的fn函数参数是真正的渲染过程,raf只是协调渲染的节奏。
raf尽量以每隔16毫秒的速度去调用传染的fn参数,如果发现上一次被调用时间和这一次被调用时间相差不足16毫秒,就会保持16毫秒一次的渲染间隔继续,如果发现两次调用时间间隔已经超出了16毫秒,就会在下一次时钟周期立刻调用fn。
还是让id为sample的元素向右移动的例子,我们定义渲染每一帧的函数render,代码如下:
var left = 0;
var animatedElement = document.getElementById("sample");
var startTimestamp = new Date().getTime();
function render(timestamp) {
left += (timestamp - startTimestamp) / 16;
animatedElement.style.left = left + 'px';
if (left < 400) {
raf(render);
}
}
上面的render函数中根据当前时间和开始动画的时间差来计算sample元素的left属性,这样无论render函数何时被调用,总能够渲染出正确的结果。
最后,我们将render作为参数传递给raf,启动了动画过程:
raf(render);
上面的例子在本书的Github代码库https://github.com/mocheng/react-and-redux的文件chapter-10/animation_types/simulate_requestAnimationFrame.html文件中可以找到。
实际上,现代浏览器提供了一个新的函数requestAnimationFrame,采用的就是上面描述的思路,不是以固定16毫秒间隔的时间去调用渲染过程,而是让脚本通过requestAnimationFrame传入一个回调函数,表示想要渲染一帧画面,浏览器会决定在合适的时间来调用给定的回调函数,而回调函数的工作是要根据逝去的时间来决定将界面渲染成什么样子。
这样一来,渲染动画的方式就改成按需要来渲染,而不是每隔16毫秒渲染固定的帧内容。
不是所有浏览器都支持requestAnimationFrame,对于不支持这个函数的浏览器,可以使用上面raf函数的方式模拟requestAnimationFrame的行为。
在后面介绍的react-motion库中,使用的就是requestAnimationFrame这样的方式。 深入浅出React和Redux