帧动画和DOM观察器
requestAnimationFrame
认识 requestAnimationFrame
设计 web
动画可以有多种选择:
- 使用
css3
的animation + keyframes
; - 使用
css3
的transition
; canvas
作图实现动画;- 借助
jQuery
动画实现; - 使用
js
原生的window.setTimeout()
或者window.setInterval()
,通过不间断移动元素位置来实现动画;前提是画面的更新频率要达到每秒60
次才能被肉眼看到流畅的动画效果。
现在 html5
新增了 window.requestAnimationFrame()
方法,该方法用来在页面重绘之前,通知浏览器调用一个指定的函数。
如果想得到连贯的逐帧动画,函数中必须重新调用 requestAnimationFrame()
。
如果想做到逐帧动画,应该调用该方法,这要求用户设计的动画函数执行先于浏览器重绘动画。通常来说,被调用的频率是每秒 60
次,但是一般会遵循 W3C
标准规定的频率。如果是后台标签页面,重绘频率则会大大降低。
requestID = window.requestAnimationFrame(callback); // Firefox23 / IE10 / Chrome / Safari 7
requestID = window.mozRequestAnimationFrame(callback); // Firefox < 23
requestID = window.webkitRequestAnimationFrame(callback); // Old versions Chrome/Webkit
参数说明如下:
-
callback
在每次需要重新绘制动画时,会调用这个参数所指定的函数。这个回调函数会收到一个参数,这个DOMHighResTimeStamp
类型的参数指示当前时间距离开始触发requestAnimationFrame
的回调的时间。 -
返回
requestID
是一个长整型非零值,作为一个唯一的标识符,可以将该值作为参数传给window.cancelAnimationFrame()
来取消这个回调函数。
我们经常使用 setInterval
和 setTimeout
制作动画效果,但是这种方式非常耗费资源,经常会出现卡顿的情况。
requestAnimationFrame
的优势如下:
- 经过浏览器优化,动画更流畅。
- 窗口没激活,动画将停止,省计算机资源。
- 更省电,尤其是对移动端。
requestAnimationFrame
的使用方式:
function animate() {
//任意操作
requestAnimationFrame(animate);
}
// 请求动画
requestAnimationFrame(animate);
有时候需要加一些控制,requestAnimationFrame()
可以像 setInterval()
一样返回一个句柄,也可以取消他。
var globalID;
function animate() {
//任意操作
globalID = requestAnimationFrame(animate);
}
// 当加时赛开始
globalID = requestAnimationFrame(animate);
// 当停止
cancelAnimationFrame(globalID);
案例:设计进度条
/*
<div id="test" style="width: 1px;height: 17px;background: #0f0;"></div>
<input type="button" value="Run" id="run" />
*/
window.requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var start = null;
var ele = document.getElementById("test");
var progress = 0;
function step(timestamp) {
progress += 1;
ele.style.width = progress + '%';
ele.innerHTML = progress + '%';
if(progress < 100) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step); // 自动执行
document.getElementById('run').addEventListener('click', function() {
ele.style.width = '1px';
progress = 0;
requestAnimationFrame(step);
}, false);
案例:设计旋转的小球
window.requestAnimationFrame = (function() {
return window.requestAnimationFrame || window.oRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame || function() {
window.setTimeout(callback, 1000/60);
}
})();
var canvas, context;
init();
animate();
function init() {
canvas = document.createElement('canvas');
canvas.style.left = 0;
canvas.style.top = 0;
canvas.width = 210;
canvas.height = 210;
context = canvas.getContext('2d');
document.body.appendChild(canvas);
}
function animate() {
requestAnimationFrame(animate);
draw();
}
function draw() {
var time = new Date().getTime() * 0.002;
var x = Math.sin(time) * 96 + 105;
var y = Math.cos(time * 0.9) * 96 + 105;
context.fillStyle = 'pink';
context.fillRect(0,0,255,255);
context.fillStyle = 'rgb(255,0,0)';
context.beginPath();
context.arc(x, y, 10, 0, Math.PI * 2, true);
context.closePath();
context.fill();
}
Mutation Observer
变动观察器,是监视 DOM 变动的接口。当 DOM 对象树发生任何变动时,Mutation Observer 就会接到通知。
Mutation Observer 类似于事件,可以理解为当 DOM 发生变动会触发 Mutation Observer 事件。但是它与事件有一个本质不同,比较如下:
- 事件是同步触发的,也就是说 DOM 发生变动立刻会触发相应的事件。
- Mutation Observer 则是异步触发,DOM 发生变动以后,并不会马上触发,而是要等到当前所有 DOM 操作都结束后才触发。
这样设计是为了应付 DOM 变动频繁的情况。
Mutation Observer 有以下特点:
- 等待所有脚本任务完成后才会运行,采用异步方式。
- 把 DOM 变动记录封装成一个数组进行处理,而不是一条条的个别处理 DOM 变动。
- 可以观察发生在 DOM 节点的所有变动,也可以观察某一类变动。
使用下面的表达式检查浏览器是否支持 API
var MutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
var MutationObserverSupper = !!MutationObserver;
使用步骤:
- 使用 MutationObserver() 构造函数,新建一个实例,同时指定这个实例的回调函数。
var observer = new MutationObserver(callback);
- 使用 observer() 方法指定所要观察的 DOM 元素,以及要观察的特定变动。
var article = document.querySelector('article');
var options = {
'childList': true,
'arrtibutes': true
}
上面代码首先要指定所要观察的 DOM 元素时 article,然后指定所要观察的变动是子元素的变动和属性变动,最后,将这两个限定条件作为参数,传入 observer 对象的 observer() 方法。
MutationObserver 所观察的 DOM 变动(即上面 option 对象),包含以下类型,设置值为 布尔值。
- childList: 子元素的变动。
- arrtibutes: 属性的变动。
- characterData: 节点内容或节点文本的变动。
除了变动类型,option 对象还可以设定以下属性,设置值为布尔值。
- attributeOldValue: 如果为 true,则表示需要记录变动前的属性值。
- characterDataOldValue: 如果为 true,则表示需要记录变动前的数据值。
- attributesFilter: 值为一个数组,表示需要观察的特定属性,如 [‘class’, ‘str’]。
- subtree: 所有下属节点(包括子节点和子节点的子节点)的变动。
- 使用 disconnect() 停止观察,发生相应变化时,不再调用回调函数。
observer.disconnect();
- 使用过 takeRecord() 清除变动记录,即不再处理未处理的变动。
observer.takeRecord
DOM 对象每次发生变化,就会生成一条变动记录。这个变动记录对应一个 MutationRecord 对象,该对象包含了与变动相关的所有信息。
- type: 观察变动的类型,如 attribute、characterData、childList。
- target: 发生变化的 DOM 对象。
- addedNodes: 新增的 DOM 对象。
- removeNodes: 删除的 DOM 对象。
- previousSibling: 前一个同级的 DOM 对象,如果没有则返回 null。
- nextSibling: 下一个同级的 DOM 对象,如果没有返回 null。
- attributeName: 发生变动的属性。如果设置了 attibuteFilter,则只返回预先指定的属性。
- oldValue: 变动前的值。这个属性只对 attribute 和 characterData 变动有效,如果发生 childList 变动,则返回 null。
- 使用 MutationObserver 对象时,可能触发的各种事件必须设定的 MutationObserver 选项值说明如下,小括号内的选项为可选项,如下:
案例:观察 DOM 元素
/*
<div id="div" style="height:100px;width: 100%;background-color:pink"></div>
<input type="button" name="" value="插入 span 元素" onclick="changeDiv()">
*/
function onchange(mutationRecords, mutationObserver) {
console.log('onchange');
console.log(mutationRecords);
console.log(mutationObserver);
}
var div = document.getElementById('div');
var mo = new window.MutationObserver(onchange);
options = {
childList: true
};
mo.observe(div, options);
function changeDiv() {
var span = document.createElement("span");
span.innerHTML = "我是一个 span 元素";
div.appendChild(span);
}
案例:观察 DOM 属性
/*
<a href="#a" id="a1">链接1</a>
<a href="#b" id="a2">链接2</a>
<input type="button" onclick="changeA()" value="修改 a 元素">
*/
var a1E1 = document.getElementById('a1');
var a2E2 = document.getElementById('a2');
var attr = ['href'];
var mo = new window.MutationObserver(onchange);
options = {
attributes: true,
attributeFilter: attr,
attributeOldValue: true
}
mo.observe(a1E1, options);
mo.observe(a2E2, options);
function changeA() {
a1E1.setAttribute('href', "http://www.baidu.com");
a2E2.setAttribute('href', "http://www.weibo.com");
}
function onchange(mutationRecords, mutationObserver) {
for(var i = 0; i < mutationRecords.length; i++) {
console.log("修改前的" + mutationRecords[i].attributeName + "属性为:" + mutationRecords[i].oldValue);
/*
修改前的href属性为:#a
修改前的href属性为:#b
*/
}
}