在用 js 写动画的时候,无非使用 setTimeout/setInterval 或者 requestAnimationFrame 来处理动画 (在 jquery 的代码里也是这么干的),本文主要为了记录下两者的区别及使用两者来实现动过程。
以实现一个简单的滚动到顶部为例
setInterval
setInterval() 方法重复调用一个函数或执行一个代码段,在每次调用之间具有固定的时间延迟。返回一个 intervalID,可用于 cancelInterval 达到结束循环的效果。
setTimeout 和 setInterval 的实现基本没区别,一个是定时执行,一个是定时循环执行,前者加个自己调用自己就是后者了,下面主要以 setInterval 为代表
实现过程:
1. 写个方法,该方法需要传入一个代表动画所需执行的时间的参数 (如:滚动到顶部需要 1000 毫秒)
- function doAnimate(duration){
- return function(){
- // do something
- }
- }
2. 取当前页面距顶部高度、滚动速度 (以匀速为例)、写个开始动画的函数 (为了给 addEventListener 绑事件传参,其实也可直接 dom.onclick = fn)
- function doAnimate(duration) {
- return function() {
- var start = document.documentElement.scrollTop;
- var scrollSpeed = start / duration * (1000 / 60); // 以大多浏览器的刷新频率60帧(60Hz)为准 1秒60次的刷新
- var timer;
- var startTime = +new Date(); // 标记时间,仅供后面测效果用而已
- function startAnimate() {
- timer = setInterval(function() {
- // do something
- },
- 1000 / 60)
- }
- }
- }
3. 写滚动动画
- function doAnimate(duration) {
- return function() {
- var start = document.documentElement.scrollTop;
- var scrollSpeed = start / duration * (1000 / 60); // 这里取到平均滚动距离,以大多浏览器的刷新频率60帧(60Hz)为准,1秒60次的刷新
- var timer;
- function startAnimate() {
- timer = setInterval(function() {
- document.documentElement.scrollTop = start < scrollSpeed ? start -= start: start -= scrollSpeed; // 当前该滚动到何处,如果距离顶部小于平均滚动距离,直接滚到scrollTop为0;如果大于,则取到的触发时高度以平均滚动距离递减
- if (start === 0) {
- clearInterval(timer);
- }
- },
- 1000 / 60)
- }
- }
- }
4. 写个很高的页面、给个 div、加个 click 事件触发滚动回顶部
- <!-- 为了更好的看到滚动效果及测滚动是否平滑,我们用某度的大图扔页面上 -->
- <img src="./来自百度壁纸的大图" />
- <img src="./来自百度壁纸的大图" />
- <img src="./来自百度壁纸的大图" />
- <div id="scrollTop_1000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:50px;right:50px;text-align: center">
- 1000
- </div>
- <div id="scrollTop_3000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:80px;right:50px;text-align: center">
- 3000
- </div>
js
- function doAnimate(duration){
- return function(){
- varstart = document.documentElement.scrollTop;
- varscrollSpeed = start/duration*(1000/60);// 这里取到平均滚动距离,以大多浏览器的刷新频率60帧(60Hz)为准,1秒60次的刷新
- var timer;
- function startAnimate(){
- timer = setInterval(function () {
- document.documentElement.scrollTop = start < scrollSpeed ? start -= start : start -= scrollSpeed;// 当前该滚动到何处,如果距离顶部小于平均滚动距离,直接滚到scrollTop为0;如果大于,则取到的触发时高度以平均滚动距离递减
- if(start === 0){
- clearInterval(timer);
- }
- },1000/60) }
- }
- }
- document.getElementById('scrollTop_1000').addEventListener('click',doAnimate(1000),!1)
- document.getElementById('scrollTop_3000').addEventListener('click',doAnimate(3000),!1)
效果如图:
截图用了霸气十足的面子果实能力者,分别测了设置 duration 为 1000 和 3000 的滚动效果 -。- 嗯... 他是个好人
requestAnimationFrame
requestAnimationFrame() 方法告诉浏览器您希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法将在重绘之前调用的回调作为参数。返回一个 requestID ,可用于 cancelAnimationFrame 达到取消 requestAnimationFrame 动画的效果。
实现思路如上,代码如下:
html
- <!-- 为了更好的看到滚动效果及测滚动是否平滑,我们用某度的大图扔页面上 -->
- <img src="./来自百度壁纸的大图" />
- <img src="./来自百度壁纸的大图" />
- <img src="./来自百度壁纸的大图" />
- <div id="scrollTop_1000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:50px;right:50px;text-align: center">
- 1000
- </div>
- <div id="scrollTop_3000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:80px;right:50px;text-align: center">
- 3000
- </div>
js
- function doAnimate(duration){
- return function(){
- varstart = document.documentElement.scrollTop;
- // 获取实时时间
- varnowTime =function(){
- return+new Date
- }
- // 开始时间 用于计算动画运行时间和动画规定时间的百分比
- varstartTime = nowTime();
- var animateId;
- varstartAnimate =function() {
- animateId = requestAnimationFrame(toTop);
- }
- varstopAnimate =function() {
- cancelAnimationFrame(animateId)
- }
- function toTop(){
- // 剩下时间
- varrestTime = Math.max(0, duration - ( nowTime() - startTime))
- varpercent = restTime / duration || 0;
- varchangeStyle =function(value){
- document.documentElement.scrollTop = value;
- }
- // 根本比例获取剩下的距离,也就是实时距离顶部的距离
- vardistance = start * percent;
- if(!distance){
- changeStyle(distance)
- stopAnimate();
- }else{
- changeStyle(distance)
- startAnimate(toTop);
- }
- }
- startAnimate(toTop);
- }
- }
- document.getElementById('scrollTop_1000').addEventListener('click',doAnimate(1000),!1)
- document.getElementById('scrollTop_3000').addEventListener('click',doAnimate(3000),!1)
效果如图:
没区别,没毛病,然而并没有和上面用同一张图...(其实打印下时间,会发现 setInterval 会是 1000 毫秒以内,大致在 960-980 毫秒之间,这个梗哪位大神可知???求解!!!)
两者的区别
requestAnimationFrame 会请求浏览器调用指定的函数在下一次重绘之前更新动画,所以开发者不用考虑频率 / 丢帧问题
setInterval 中,会因为浏览器显示频率和 JavaScript 单线程可能会引发阻塞的问题而导致丢帧 (视觉应为动画不流畅)
requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,性能方面更出色
对于隐藏或者不可见的元素,requestAnimationFrame 将不会进行重绘或回流,这点可减少 cpu,gpu 及内存的负荷
setInterval 兼容一些老版本的浏览器 (jquery 保留这个应该也是为了兼容老版本浏览器...)
requestAnimationFrame 兼容图
顺便扔上 jquery 里 animate 的部分代码:
- jQuery.fx.start =function() {
- if( !timerId ) {
- timerId = window.requestAnimationFrame
- window.requestAnimationFrame( raf ) :
- window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
- }
- };
- jQuery.fx.stop =function() {
- if ( window.cancelAnimationFrame ) {
- window.cancelAnimationFrame( timerId );
- } else {
- window.clearInterval( timerId );
- }
- timerId =null;
- };
这里的 jQuery.fx.interval 默认值为 13 ,也是因为显示频率的问题~
略显尴尬... 在我 windows 上和 mac 上也保留一些老版本的浏览器,测效果的结果简直蛋疼... 看来兼容方面还是需要做处理的,天将降大任于 setInterval 啊 :-D
面子果实都登场了,小伙伴们是不是也该顶一顶了呢 [偷笑][偷笑][偷笑] 欢迎交流 欢迎指出各个问题~
来源: http://www.cnblogs.com/ys-ys/p/6919395.html