第三十九课: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了。
加油!
第三十九课:requestAnimationFrame详解的更多相关文章
- NeHe OpenGL教程 第三十九课:物理模拟
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- Java进阶(三十二) HttpClient使用详解
Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...
- 十九、linux--RAID详解
一.什么是RADI Raid是廉价冗余磁盘阵列,简称磁盘阵列. 运维人员就叫RAID.Raid是一种把多块独立的磁盘(物理磁盘)按不同方式组合起来形成一个磁盘组,在逻辑上看起来就是一个大的磁盘,从而提 ...
- shell学习三十四天----printf详解
http://blog.csdn.net/shanyongxu/article/details/46744055
- 潭州课堂25班:Ph201805201 django 项目 第三十九课 后台 文章发布,图片上传到 FastDFS后端实现 七牛云讲解(课堂笔记)
文章发布: # 1,从前台获取参数# 2,校验参数# 3,把数据保存到数据库# 4,返回执行结果到前台,(创建成功或失败) 自定义 froms.py 校验参数 上传图片到七牛云 注册 https:// ...
- python第三十九课——面向对象(二)之初始化属性
设计Car类,初始化属性speed,提供一个run函数 import time class Car: def __init__(self,speed): self.speed=speed #将Road ...
- python第三十九课——面向对象(二)之设计类
1.设计类class 车: #属性 颜色 = red 品牌 = "BMW" 车牌 = "沪A88888" #函数 行驶(): 停止(): 2.实例化车对象 ca ...
- Java并发编程原理与实战三十二:ForkJoin框架详解
1.Fork/Join框架有什么用呢? ------->Fork使用来切分任务,Join是用来汇总结果.举个简单的栗子:任务是1+2+3+...+100这个任务(当然这个任务的结果有好的算法去做 ...
- jQuery 源码解析(三十) 动画模块 $.animate()详解
jQuery的动画模块提供了包括隐藏显示动画.渐显渐隐动画.滑入划出动画,同时还支持构造复杂自定义动画,动画模块用到了之前讲解过的很多其它很多模块,例如队列.事件等等, $.animate()的用法如 ...
随机推荐
- jenkins 更换主数据目录
工作中,由于Jenkins默认的主目录空间太小,导致需要将Jenkins默认的主目录修改到其它目录.本文针对更改Jenkins的主目录详细介绍. 注意:在Jenkins运行时是不能更改的. 请先将Je ...
- hadooop2.6 job pending research
https://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-common/ClusterSetup.html 我使用的是已经运行在 ...
- pixel art之 hqx 算法
在去年的时候,偶然看到hqx算法. 一个高质量的插值放大算法. 与双线性插值等插值算法相比,这个算法放大后对人眼保护相对比较好. 没有双线性插值看起来模糊,固然,也抽空把算法简单优化了一下. 官网及代 ...
- AC日记——信息传递 洛谷 P2661 (tarjan求环)
题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一 ...
- AndroidStudio权威教程 AS添加第三方库的6种方式(Jar module so等)
点击项目设置按钮 依次选择 App > Dependencies 1. 直接搜索法 依次选择 + > Library dependency 这里的搜索一定要是全名的,不然搜不到哦 下图所表 ...
- NSURLSession学习笔记
NSURLSession学习笔记(一)简介 一.URL Session的基本概念 1.三种工作模式: 默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁 ...
- f2fs源码解析(五) node管理结构梳理
node是f2fs重要的管理结构, 它非常重要! 系统挂载完毕后, 会有一个f2fs_nm_info结构的node管理器来管理node的分配. f2fs_nm_info中最让人疑惑的是几颗基数树: s ...
- C# 改变无边框窗体的尺寸大小
以下代码为修改窗体尺寸的代码: const int HTLEFT = 10; ; ; ; ; ; const int HTBOTTOMLEFT = 0x10; ; protected override ...
- [资料]Keychain 获取设备唯一
BAIDU http://blog.csdn.net/wonengxing/article/details/42142595 http://www.cnblogs.com/max5945/archiv ...
- Linux 结束进程
一个进程由于以下5个原因中的一个终止 --main函数调用return; --调用exit函数--C语言库函数: --调用_exit函数--系统调用 --调用abort函数 --被一个信号终止.(ki ...