第三十九课:requestAnimationFrame详解

大家应该都知道,如果一个页面运行的定时器很多,无论你怎么优化,最后肯定会超过指定时间才能完成动画。定时器越多,延时越严重。

为此,YUI,kissy等采用中央队列的方式,将定时器减少至一个。浏览器厂商也因此原生支持了requestAnimationFrame方法,此方法基本上能保证每秒刷新60次。但是此方法在还没形成标准之前,很多低版本浏览器是不支持的,比如:IE9以及以下版本,不过谷歌和火狐都用私有的方法名实现了requestAnimationFrame方法。比如:谷歌:webkitRequestAnimationFrame,,火狐:mozRequestAnimationFrame。形成标准后,IE10才开始支持,由于IE10支持的是标准的requestAnimationFrame方法,因此它没有私有前缀,所以并不存在msRequestAnimationFrame。

我们先来看一下requestAnimationFrame方法是如何使用的?

var startTime,duration = 3000,requestID;

function animate(now){   //webkitRequestAnimationFrame方法会给回调函数中传入一个当前时间的参数。

  var per = (now - startTime) / duration;

  if(per >=1){

    //动画结束

  }else{

    div.style.left = Math.round(600*per) + "px";

    window.webkitRequestAnimationFrame(animate);   //此方法调用一次只会重绘一次动画,如果需要连续的动画,则需要重复调用

  }

}

function start(){

  startTime = Date.now();

  requestID = window.webkitRequestAnimationFrame(animate);  //此方法可以传入两个参数,第一个是回调,第二个是执行动画的元素节点(可选),返回一个ID。

}

div.onclick = start;

上面的这个例子,是针对chrome浏览器实现的。

那么,我们如何来写出兼容性的写法呢?

第一个版本:

window.requestAnimationFrame = (function(){

  return window.requestAnimationFrame ||    //IE10以及以上版本,以及最新谷歌,火狐版本

      window.webkitRequestAnimationFrame ||   //谷歌老版本

       window.mozRequestAnimationFrame ||   //火狐老版本

        function(callback){    //IE9以及以下版本

          window.setTimeout(callback , 1000/60);  //这里强制让动画一秒刷新60次,这里之所以设置为16.7毫秒刷新一次,是因为requestAnimationFrame默认也是16.7毫秒刷新一次。

        }

})();

上面这个兼容性写法,有几个问题,第一个:没有解决cancelAnimationFrame方法的兼容性写法。第二个:强制让IE9-浏览器,动画绘制间隔为16.7ms,但是这些浏览器的绘制间隔并不都是这个值。第三个:火狐老版本的mozRequestAnimationFrame方法跟标准的requestAnimationFrame方法实现有些出入,比如:早期火狐的此方法,不支持传参。第四个:老版本的webkit,在有些版本下,此方法不会返回id,还有一些版本没有给回调函数传当前时间的参数。

我的理解:至于老版本的火狐和老版本的webkit,我个人觉得没有必要去兼容,只要兼容IE9-浏览器就OK了。因此以上的4个问题,只存在前面两个。那如果你想兼容第三个和第四个问题的话,请去看司徒正美基于网友屈屈与月影的版本改进而来的版本:https://github.com/wedteam/qwrap-components/blob/master/animation/anim.frame.js。

第二个版本,解决上面的第一个问题和第二个问题:

(function() {
  var lastTime = 0;
  var version = [‘webkit‘, ‘moz‘];
  for(var i = 0; i < version.length && !window.requestAnimationFrame; i++) {   //如果此浏览器不支持requestAnimationFrame方法,就循环遍历version数组
    window.requestAnimationFrame = window[version[i] + ‘RequestAnimationFrame‘];
    window.cancelAnimationFrame = window[version [i] + ‘CancelAnimationFrame‘] ||         // 有一些Webkit版本中,此方法的名字改变了
      window[version [i] + ‘CancelRequestAnimationFrame‘];
  }

  if (!window.requestAnimationFrame) {   //如果是IE9-浏览器
    window.requestAnimationFrame = function(callback, element) {      //我们使用上一个例子来讲解这段代码。当我们点击div时,就会触发start方法,我们假设当前时间为11111,设置startTime=11111, 调用requestAnimationFrame(animate)方法,这时,当前时间,我们假设是currTime = 11112,lastTime = 0,这时timeToCall = 0,因此调用setTimeout(function(){},0),把lastTime = 11111,返回id。过了浏览器的最小时间后,我们假设是4ms,就会立即执行animate(11112)。这时就会继续执行requestAnimationFrame。假设当前时间是11118,timeToCall = 9.7,这时lastTime = 11127.7,当前时间为11127.7时,就执行animate(11127.7),per = 16.7 / 3000,继续执行requestAnimationFrame....
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));   //timeToCall的值为0-16.7之间。

      var id = window.setTimeout(function() {
        callback(currTime + timeToCall);
      }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }
  if (!window.cancelAnimationFrame) {   
    window.cancelAnimationFrame = function(id) {
      clearTimeout(id);
    };
  }
})();

当然,requestAnimationFrame不是没有缺点,它不能控制fps(60),我们在以下场景下就不能使用。比如:做一些慢放动作,fps<60的情况下;还有在动作,枪战,飞车等场景下,fps需要>60的情况下,像这种场景下,如果帧数不高,画面会模糊。利用setTimeout(IE9,10,Firefox,chrome等,它的最短时间间隔已经压缩至4ms了)我们可以轻松跑到100帧以上的动画,能让画面更清楚,细节更逼真。

另外,postMessage这个异步方法,能实现超高度的动画,有人做过实验:

setTimeout                      平均帧数200

requestAnimationFrame    平均帧数60           

loop(循环)                     平均帧数200-300

postMessage                    平均帧数900-1000

var testing = true;   //用来停止动画的,也就是停止代码执行的

function main(){

  //记录两次执行时间的间隔

}

function run1(){       //点击按钮1,执行run1方法,然后使用setTimeout方法不断的执行main方法,main方法会记录每次执行的时间,求出两次执行时间的间隔。

  main();

  if(testing){

    setTimeout(run1, 1);

  }

}

function run2(){    //点击按钮2,执行run2方法

  main();

  if(testing){

    window.requestAnimationFrame(run2);

  }

}

function run3(){   //点击按钮3,执行run3方法

  var count = 15;

  while(count--){         //利用while循环执行main方法,记录两个循环操作之间的时间间隔。

    main();

  }

  if(testing){

    setTimeout(run3,1);    //当然这里会有一点点的误差,因为用到了setTimeout方法,这样我们可以设置testing=false,停止循环调用main,如果直接用while(true),那么无法停止此循环。

  }

}

window.addEventListener("message",run4,false);   //绑定message事件,只要调用postMessage方法,就会触发message事件。

function run4(){

  main();

  if(testing){

    window.postMessage("","*");

  }

}

IE10也有一个高效的异步方法setImmediate。

在现实中,尤其是游戏开发,我们要结合多种异步API。比如:作为背景的树木,流水等用requestAnimationFrame方法,玩家角色,由于需要速度的变化,那么用setTimeout比较合适,一些非常炫的动画,可能就需要postMessage,setImmediate,Image.onerror等API了。

加油!

时间: 2024-11-01 07:29:25

第三十九课:requestAnimationFrame详解的相关文章

NeHe OpenGL教程 第三十九课:物理模拟

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十九课:物理模拟 物理模拟简介: 还记得高中的物理吧,直线运动,自由落体运动,弹簧.在这一课里,我们将创造这一切. 物理模拟介绍 如果你很熟悉物理规律,并且想实现它,这篇文章很适合你. 在这篇教程里,你会创建一个非常简单的物理引

Java进阶(三十二) HttpClient使用详解

Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户端发送Http请求变得容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性.因此熟练掌握HttpClient是很重要的必修内容,掌握HttpClient后,相信对于Http协议的了解会更加深入. 一.简介 HttpClient是A

十九、游标详解

代码中被[]包含的表示可选,|符号分开的表示可选其一. 需求背景 当我们需要对一个select的查询结果进行遍历处理的时候,如何实现呢? 此时我们需要使用游标,通过游标的方式来遍历select查询的结果集,然后对每行数据进行处理. 本篇内容 游标定义 游标作用 游标使用步骤 游标执行过程详解 单游标示例 嵌套游标示例 准备数据 创建库:javacode2018 创建表:test1.test2.test3 /*建库javacode2018*/ drop database if exists jav

JAVA学习第三十九课(常用对象API)- 集合框架(七)— Map集合及其子类对象

一.Map集合常见子类 HashTable:内部结构是哈希表,同步,此实现提供所有可选的映射操作,不允许使用 null 值和 null 键 (HashTable下有子类Properties,使用频率非常高,用来存储键值对型的配置文件信息和IO技术相结合) HashMap:内部结构是哈希表,不同步,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键. TreeMap:内部结构是二叉树,不同步,可以对Map集合中的键进行排序. 二.HashMap演示 import java.ut

shell学习三十四天----printf详解

printf 先来看一个简单的例子:使用命令printf "hello,world\n", 输出:hello,world 再使用echo "hello,world\n",输出为:hello,world\n 案例二:使用命令printf "%s\n" hello,world 输出结果为:hello,world printf命令的完整语法有两个部分: printg format-string [arguments] 第一部分为描述格式规格的字符串,他

第三十九课

scsi及iscsi的基础原理 CentOS上的iscsi的使用 keepalived的基础应用 keepalived的高级应用 一. 二.keepalived基础应用

AGG第三十九课 rasterizer_scanline_aa画线函数疑惑

头文件 #include<agg_rasterizer_scanline_aa.h> 类型 template<class Clip =rasterizer_sl_clip_int> class rasterizer_scanline_aa 成员函数 void add_path(VertexSource&vs,unsigned path_id=0)加入顶点源 void reset() 清空上一次的渲染缓存数据,每次调用add_path函数之前都需要执行该操作 其中也提供了简单

十九、linux--RAID详解

一.什么是RADI Raid是廉价冗余磁盘阵列,简称磁盘阵列. 运维人员就叫RAID.Raid是一种把多块独立的磁盘(物理磁盘)按不同方式组合起来形成一个磁盘组,在逻辑上看起来就是一个大的磁盘,从而提供比单个磁盘更大的存储容量或更高的存储性能,同时又提供不同级别数据冗余备份的一种技术. 所以优点为:存储量大.性能高.冗余 二.Raid级别介绍 把多个物理磁盘通过不同的技术方法组合成磁盘阵列,这个不同的方法就是RAID级别. RAID级别有很多,这里有:Raid0   Raid1  Raid10

三十九、git add详解

一.前言git add命令主要用于把我们要提交的文件的信息添加到索引库中.当我们使用git commit时,git将依据索引库中的内容来进行文件的提交.二.基本git add <path>表示 add to index only files created or modified and not those deleted 我通常是通过git add <path>的形式把我们<path>添加到索引库中,<path>可以是文件也可以是目录.git不仅能判断出&