2014年9月6日星期六

深入理解requestAnimationFrame - 码农终结者

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
深入理解requestAnimationFrame - 码农终结者  阅读原文»

前言

本文主要参考w3c资料,从底层实现原理的角度介绍了requestAnimationFrame、cancelAnimationFrame,给出了相关的示例代码以及我对实现原理的理解和讨论。

本文介绍

浏览器中动画有两种实现形式:通过申明元素实现(如SVG中的元素)和脚本实现。
可以通过setTimeout和setInterval方法来在脚本中实现动画,但是这样效果可能不够流畅,且会占用额外的资源。可参考《Html5 Canvas核心技术》中的论述:

它们有如下的特征:
1、即使向其传递毫秒为单位的参数,它们也不能达到ms的准确性。这是因为javascript是单线程的,可能会发生阻塞。
2、没有对调用动画的循环机制进行优化。
3、没有考虑到绘制动画的最佳时机,只是一味地以某个大致的事件间隔来调用循环。
其实,使用setInterval或setTimeout来实现主循环,根本错误就在于它们抽象等级不符合要求。我们想让浏览器执行的是一套可以控制各种细节的api,实现如"最优帧速率"、"选择绘制下一帧的最佳时机"等功能。但是如果使用它们的话,这些具体的细节就必须由开发者自己来完成。

requestAnimationFrame不需要使用者指定循环间隔时间,浏览器会基于当前页面是否可见、CPU的负荷情况等来自行决定最佳的帧速率,从而更合理地使用CPU。

本文主要内容

名词说明

  • 动画帧请求回调函数列表

每个Document都有一个动画帧请求回调函数列表,该列表可以看成是由< handle, callback>元组组成的集合。其中handle是一个整数,唯一地标识了元组在列表中的位置;callback是一个无返回值的、形参为一个时间值的函数(该时间值为由浏览器传入的从1970年1月1日到当前所经过的毫秒数)。
刚开始该列表为空。

  • Document

Dom模型中定义的Document节点。

  • Active document

浏览器上下文browsingContext中的Document被指定为active document。

  • browsingContext

    浏览器上下文。

浏览器上下文是呈现document对象给用户的环境。
浏览器中的1个tab或一个窗口包含一个顶级浏览器上下文,如果该页面有iframe,则iframe中也会有自己的浏览器上下文,称为嵌套的浏览器上下文。

  • DOM模型

详见我的理解DOM

  • document对象

当html文档加载完成后,浏览器会创建一个document对象。它对应于Document节点,实现了HTML的Document接口。
通过该对象可获得整个html文档的信息,从而对HTML页面中的所有元素进行访问和操作。

  • HTML的Document接口

该接口对DOM定义的Document接口进行了扩展,定义了 HTML 专用的属性和方法。
详见The Document object

  • 页面可见

当页面被最小化或者被切换成后台标签页时,页面为不可见,浏览器会触发一个 visibilitychange事件,并设置document.hidden属性为true;切换到显示状态时,页面为可见,也同样触发一个 visibilitychange事件,设置document.hidden属性为false。
详见Page VisibilityPage Visibility(页面可见性) API介绍、微拓展

  • 队列

浏览器让一个单线程共用于执行javascrip和更新用户界面。这个线程通常被称为"浏览器UI线程"。
浏览器UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲。一旦空闲,队列中的下一个任务就被重新提取出来并运行。这些任务要么是运行javascript代码,要么执行UI更新,包括重绘和重排。

API接口

Window对象定义了以下两个接口:

partial interface Window {
long requestAnimationFrame(FrameRequestCallback callback);
void cancelAnimationFrame(long handle);
};

requestAnimationFrame

requestAnimationFrame方法用于通知浏览器重采样动画。
当requestAnimationFrame(callback)被调用时不会执行callback,而是会将元组< handle,callback>插入到动画帧请求回调函数列表末尾(其中元组的callback就是传入requestAnimationFrame的回调函数),并且返回handle值,该值为浏览器定义的、大于0的整数,唯一标识了该回调函数在列表中位置。
每个回调函数都有一个布尔标识cancelled,该标识初始值为false,并且对外不可见。
在后面的"处理模型"中我们会看到,浏览器在执行"采样所有动画"的任务时会遍历动画帧请求回调函数列表,判断每个元组的callback的cancelled,如果为false,则执行callback。

cancelAnimationFrame

cancelAnimationFrame 方法用于取消先前安排的一个动画帧更新的请求。
当调用cancelAnimationFrame(handle)时,浏览器会设置该handle指向的回调函数的cancelled为true。
无论该回调函数是否在动画帧请求回调函数列表中,它的cancelled都会被设置为true。
如果该handle没有指向任何回调函数,则调用cancelAnimationFrame 不会发生任何事情。

处理模型

当页面可见并且动画帧请求回调函数列表不为空时,浏览器会定期地加入一个"采样所有动画"的任务到UI线程的队列中。

此处使用伪代码来说明"采样所有动画"任务的执行步骤:

var list = {};

var browsingContexts = 浏览器顶级上下文及其下属的浏览器上下文;

for (var browsingContext in browsingContexts) {
var time = 从1970年1月1日到当前所经过的毫秒数;
var d = browsingContext的active document; //即当前浏览器上下文中的Document节点
//如果该active document可见
if (d.hidden !== true) {
//拷贝active document的动画帧请求回调函数列表到list中,并清空该列表
var doclist = d的动画帧请求回调函数列表
doclist.appendTo(list);
clear(doclist);
}

//遍历动画帧请求回调函数列表的元组中的回调函数
for (var callback in list) {
if (callback.cancelled !== true) {
try {
//每个browsingContext都有一个对应的WindowProxy对象,WindowProxy对象会将callback指向active document关联的window对象。
//传入时间值time
callback.call(window, time);
}
//忽略异常
catch (e) {
}
}
}
}

已解决的问题

  • 为什么在callback内部执行cancelAnimationFrame不能取消动画?

问题描述
如下面的代码会一直执行a:

var id = null;

function a(time) {
console.log("animation");
window.cancelAnimationFrame(id); //不起作用
id = window.requestAnimationFrame(a);
}

a();

原因分析
我们来分析下这段代码是如何执行的:
1、执行a
(1)执行"a();",执行函数a;
(2)执行"console.log("animation");",打印"animation";
(3)执行"window.cancelAnimationFrame(id);",因为id为null,浏览器在动画帧请求回调函数列表中找不到对应的callback,所以不发生任何事情;
(4)执行"id = window.requestAnimationFrame(a);",浏览器会将一个元组< handle, a>插入到Document的动画帧请求回调函数列表末尾,将id赋值为该元组的handle值;

2、a执行完毕后,执行第一个"采样所有动画"的任务
假设当前页面一直可见,因为动画帧请求回调函数列表不为空,所以浏览器会定期地加入一个"采样所有动画"的任务到线程队列中。
a执行完毕后的第一个"采样所有动画"的任务执行时会进行以下步骤:
(1)拷贝Document的动画帧请求回调函数列表到list变量中,清空Document的动画帧请求回调函数列表;
(2)遍历list的列表,列表有1个元组,该元组的callback为a;
(3)判断a的cancelled,为默认值false,所以执行a;
(4)执行"console.log("animation");",打印"animation";
(5)执行"window.cancelAnimationFrame(id);",此时id指向当前元组的a(即当前正在执行的a),浏览器将当前元组的a的cancelled设为true。
(6)执行"id = window.requestAnimationFrame(a);",浏览器会将新的元组< handle, a>插入到Document的动画帧请求回调函数列表末尾(新元组的a的cancelled为默认值false),将id赋值为该元组的handle值。

3、执行下一个"采样所有动画"的任务
当下一个"采样所有动画"的任务执行时,会判断动画帧请求回调函数列表的元组的a的cancelled,因为该元组为新插入的元组,所以值为默认值false,因此会继续执行a。
如此类推,浏览器会一直循环执行a。

解决方案
有下面两个方案:
1、执行requestAnimationFrame之后再执行cancelAnimationFrame。
下面代码只会执行一次a:

var id = null;

function a(time) {
console.log("animation");
id = window.requestAnimationFrame(a);
window.cancelAnimationFrame(id);
}

a();

2、在callback外部执行cancelAnimationFrame。
下面代码只会执行一次a:

function a(time) {
console.log("animation");
id = window.requestAnimationFrame(a);
}

a();
window.cancelAnimationFrame(id);

因为执行"window.cancelAnimationFrame(id);"时,id指向了新插入到动画帧请求回调函数列表中的元组的a,所以 "采样所有动画"任务判断元组的a的cancelled时,该值为true,从而不再执行a。

注意事项

1、在处理模型中我们已经看到,在遍历执行拷贝的动画帧请求回调函数列表中的回调函数之前,Document的动画帧请求回调函数列表已经被清空了。因此如果要多次执行回调函数,需要在回调函数中再次调用requestAnimationFrame将包含回调函数的元组加入到Document的动画帧请求回调函数列表

组建自动化工具Ant - 孔东阳  阅读原文»

组建自动化工具Ant

Ant可以帮助我们自动化的完成项目的构建

下面是维基百科对Ant的介绍:http://zh.wikipedia.org/wiki/Apache_Ant

Apache Ant,是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。由Apache软件基金会所提供。默认情况下,它的buildfile(XML文件)名为build.xml。每一个buildfile含有一个<project>和至少一个默认的<target>,这些targets包含许多task elements。每一个task element有一个用来被参考的id,此id必须是唯一的。

1.安装
apache-ant-1.9.4-bin.zip:http://ant.apache.org/bindownload.cgi


设置环境变量:Path+=D:\Idea\config\apache-ant-1.9.4\bin
打开命令行,执行ant,显示build.xml文件不存在,说明Ant已经安装好了

C:\Users\yuki>ant
Buildfile: build.xml does not exist!
Build failed

C:\Users\yuki>

2.编译
ant运行后会找build.xml文件
D:\Ant\app1下新建HelloWorld.javabuild.xml
在project标签下添加javac标签

<?xml version="1.0" encoding="UTF-8"?>

<project default="execute">
<!-- 编译 -->
<target name="compile">
<javac destdir="." srcdir="."></javac>
</target>
</project>

cmd进入这个目录,执行ant

D:\Ant\app1>ant
Buildfile: D:\Ant\app1\build.xml

compile:
[javac] D:\Ant\app1\build.xml:5: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; se
t to false for repeatable builds
[javac] Compiling 1 source file to D:\Ant\app1

BUILD SUCCESSFUL
Total time: 2 seconds

D:\Ant\app1>

D:\Ant\app1\HelloWorld.class文件会出现,编译成功

3.执行
修改build.xml文件,添加java标签

D:\Ant\app1\build.xml

<?xml version="1.0" encoding="UTF-8"?>

<project default="execute">
<!-- 运行 -->
<target name="execute">
<java classpath="." classname="HelloWorld"></java>
</target>
</project>

D:\Ant\app1>ant
Buildfile: D:\Ant\app1\build.xml

execute:
[java] hello world!

BUILD SUCCESSFUL
Total time: 0 seconds

D:\Ant\app1>

4.编译且执行
每一次都修改文件似乎不太好
删除HelloWorld.class,修改build.xml文件,
[name="execute"]的target上添加depends="compile"

<?xml version="1.0" encoding="UTF-8"?>

<project default="execute">
<!-- 编译 -->
<target name="compile">
<javac destdir="." srcdir="."></javac>
</target>

没有评论:

发表评论