requestAnimationFrame()
早期动画循环
JS的动画很长时间以来都是使用计时器setInterval来达成的。就像这样:
(function(){
function updateAnimations(){
doAnimation1();
doAnimation2();
}
setInterval(updateAnimations, 100);
})()
但是这样使用是有问题的,最大的问题就是时间间隔的问题。不能太长,否则看起来就会卡卡的,也不能太短,更新速度超过了屏幕的刷新速度会造成丢帧。
而且浏览器的计时器其实精度是有限的,精度最高的chrome为4ms。且在页面没有显示在屏幕上的时候,大多数浏览器会对计时器的运行频率做出限制。
这样的动画绘制机制就造成了绘制下一帧动画的时机我们并不能准确掌握。最好的结果应该是正好在屏幕刷新的那一刻绘制下一帧,也就是动画绘制的速度与电脑屏幕刷新速度一致。然而由于上述问题,这个最优的结果很难达成。于是Mozilla就提出了 requestAnimationFrame()方法。
requestAnimationFrame()
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.width != "100%"){
mozRequestAnimationFrame(updateProgress);
}
}
mozRequestAnimationFrame(updateProgress);
这个方法接收一个函数,这个函数会在下次屏幕刷新时调用,把动画写在这里就保证了动画下一帧的绘制是在屏幕刷新时同步绘制的。这个方法只会在下次屏幕刷新时调用一次,这样设计的目的是可以让开发人员方便的决定是否继续动画。想继续就继续调用这个函数就好。
有个问题就是这个动画的API还不成熟,各个厂家的实现都还带自己的前缀,使用下面这个方法可以统一接口。
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Page Visibility API
用户是否真的正在与页面交互是我们需要知道的。如果页面最小化了或隐藏起来了,那么有些功能是可以停下来的。API由3部分组成:
- document.hidden:表示页面是否隐藏的布尔值
- document.visibilityState:页面在后台,页面在前台,页面隐藏但正在被预览,页面在屏幕外执行预渲染处理
- visibilitychange:在可见和不可见转换时触发的事件
function handleVisibilityChange(){
var output = document.getElementById("myDiv"),
msg;
if (document.hidden || document.msHidden || document.webkitHidden){
msg = "Page is now hidden. " + (new Date()) + "<br>";
} else {
msg = "Page is now visible. " + (new Date()) + "<br>";
}
output.innerHTML += msg;
}
EventUtil.addHandler(document, "msvisibilitychange", handleVisibilityChange);
EventUtil.addHandler(document, "webkitvisibilitychange", handleVisibilityChange);
同样,这个API暂时需要浏览器前缀。
Geolocation API
这个API让JS可以通过浏览器来获取用户的地理位置。当然,这是需要获得用户同意的。这个API在浏览器中的实现是navigator.geolocation。这个对象有3个方法:
getCurrentPosition()
这个方法接收3个参数,成功的回调函数,可选的失败的回调函数,可选的选项对象。
成功的回调函数会接收到一个Position对象参数,它有两个属性:coords是代表位置信息的对象(latitude、longitude、accuracy、altitude、altitudeAccuracy、heading、speed),timestamp是时间戳。
失败的回调函数会接收到一个包含错误信息的对象,包含message和code。里面有失败的原因和代码。
第三个参数是一个选项对象。用来配置获取信息的方式,可以设置的值有3个,enableHighAccuracy(布尔值,是否尽可能准确的获取信息),timeout(等待位置信息返回的最长时间),maximumAge(上一次取得坐标信息的有效时长,如果没到这个时长时再次尝试获取,会使用上次的位置信息)。
navigator.geolocation.getCurrentPosition(
function(position){
alert(position.coords.latitude+"####"+positions.coords.longitude);
},
function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message); },
{
enableHighAccuracy: false,
timeout: 5000,
maximumAge: 25000
});
watchPosition()
这个方法可以跟踪用户的位置,他的使用和上面的方法一样。只不过在第一次获取位置信息后这个方法会等待系统发出位置改变的信号再次调用。这个方法会返回一个ID用来跟踪,基于这个ID可以使用clearWatch()方法清除监控。
var watchId = navigator.geolocation.watchPosition(
function(position){
alert(position.coords.latitude+"####"+positions.coords.longitude);
},
function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message);
});
navigator.geolocation.clearWatch(watchId);
IE9+、Firefox 3.5+、Opera 10.6+、Safari 5+、ChromeiOS版 Safari、Android 版 WebKit
File API
========
不能直接访问用户计算机中的文件一直都是Web应用的一大障碍。File API旨在为Web开发人员提供一种安全的方式来访问用户的文件。
支持File API的有IE10+、Firefox 4+、Safari 5.0.5+、Opera 11.1+、 Chrome。
HTML5在DOM中为文件输入元素添加了一个files集合,在通过文件输入字段选择了一个或多个文件时,files集合中将包含一组File对象。 每个File对象对应一个文件,每个都有下列只读属性:name、size、type( MIME类型)、lastModifiedDate。
FileReader
这个类型实现的是一种异步文件读取的机制,它提供了以下几种方法:
- readAsText(file,encoding):以纯文本形式读取文件,将读取到的文件保存到result属性中
- readAsDataURL(file):读取文件并将文件以数据URI的形式保存在result属性中
- readAsBinaryString(file)
- readAsArrayBuffer(file)
由于是异步读取,所以有几个事件:progress、error、load
progress事件和Ajax的一样,有lengthComputable、loaded、total属性。
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var info = "",
output = document.getElementById("myDiv"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
type = "default",
reader = new FileReader();
if (/image/.test(files[0].type)){
reader.readAsDataURL(files[0]);
type = "image";
} else {
reader.readAsText(files[0]);
type = "text";
}
reader.onerror = function(){
output.innerHTML = "Could not read file, error code is " +
reader.error.code;
};
reader.onprogress = function(event){
if (event.lengthComputable){
progress.innerHTML = event.loaded + "/" + event.total;
}
};
reader.onload = function(){
var html = "";
switch(type){
case "image":
html = "<img src=\"" + reader.result + "\">";
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});
读取部分内容
有时候我们只想读取部分文件,File对象有个slice()方法。
function blobSlice(blob, startByte, length){
if (blob.slice){
return blob.slice(startByte, length);
} else if (blob.webkitSlice){
return blob.webkitSlice(startByte, length);
} else if (blob.mozSlice){
return blob.mozSlice(startByte, length);
} else {
return null;
}
}
var blob = blobSlice(files[0], 0, 32);
reader = new FileReader();
reader.readAsDataURL(files[0]);
对象URL
也叫blob URL,指的是引用保存在File或Blob中数据的URL。使用对象URL的好处是不必把文件读到JS中就可以直接使用文件内容。只需要在使用文件内容的地方提供对象URL即可。要创建对象URL可以使用createObjectURL方法。各浏览器不同,可以hack一下。
function createObjectURL(blob){
if (window.URL){
return window.URL.createObjectURL(blob);
} else if (window.webkitURL){
return window.webkitURL.createObjectURL(blob);
} else {
return null;
}
}
这个函数返回一个URL字符串,指向一块内存的地址。所以直接当URL用就好。这个和readAsDataURL(file)返回的东西是一样的。
如果不需要相应数据了,就放开内存,但是只要有代码在引用对象URL,内存就不会释放。可以把URL传给revokeObjectURL()来释放。
读取拖放的文件
在页面上创建自定义的放置目标,使用HTML5的拖放API和文件API可以做出令人瞩目的用户界面。
var droptarget = document.getElementById( "droptarget");
function handleEvent(event){
var info = "",
output = document.getElementById("myDiv"),
files,
i,
len;
EventUtil.preventDefault(event);
if (event.type == "drop"){
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len){
info += files[i].name + " (" + files[i].type + ", " + files[i].size +
" bytes)<br>";
i++;
}
output.innerHTML = info;
}
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
使用XHR上传文件
File可以访问文件内容,所以利用这一点可以通过XHR直接把文件上传到服务器。
使用FormData类型,append一个File类型,再把FormData传递给XHR的send方法,就可以吧文件传给服务器啦。
if (event.type == "drop"){
data = new FormData();
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len){
data.append("file" + i, files[i]);
i++;
}
xhr = new XMLHttpRequest();
xhr.open("post", "hahaha.jsp", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
alert(xhr.responseText);
}
};
xhr.send(data);
}
Web计时
为了度量页面性能的API,核心是window.performance对象
performance.navigation
包含与页面导航有关的多个属性
redirectCount:页面加载前重定向数
type:页面导航类型(第一次加载,重载,前进/后退)
performance.timing
这个对象的属性都是时间戳,储存着页面载入时各个阶段的时间。
navigationStart
unloadEventStart
unloadEventEnd
redirectStart
redirectEnd
fetchStart
domainLookupStart
domainLookupEnd
connectStart
connectEnd
secureConnectionStart
requestStart
responseStart
responseEnd
domLoading
domInteractive
domContentLoadedEventStart
domContentLoadedEventEnd
domComplete
loadEventStart
loadEventEnd
Web Workers
===
JS正在像越来越复杂的方向发展,有时长时间运行JS是很常见的,但这会导致冻结用户界面,Web Workers通过使JS在后台运行解决了这个问题。
使用Worker
实例化Worker并传入要执行的JS文件名就创建了一个新的Web Worker。
var worker = new Worker("stufftodo.js");
这时浏览器会开始下载JS文件,但是并不会运行。
当我们给Worker实例发送消息时,Worker才会开始执行 。发送的数据可以是任何可以序列化的值,包括对象。
worker.postMessage({
type: "command",
message: "start! "
});
在Worker中会接到这个消息并开始执行这个JS文件。
Worker也会向页面发回消息,包括数据和报错。在本页面中,使用两个事件来接收它们。
worker.onmessage = function(event){
var data = event.data;
}
worker.onerror = function(event){
console.log("ERROR: " + event.filename + " (" + event.lineno + "): " +
event.message);
};
错误处理一定要有,否则Worker会失败的悄无声息。
想要终止Worker:
worker.terminate();
Worker全局作用域
Web Worker中所执行的代码完全在另一个作用域中,与当前代码不共享作用域。在其自己的作用域中有自己的全局对象和方法。不过就目前来说,Worker中的代码不能访问DOM,也不能影响页面外观,它能做的就是处理页面发送给它消息中的数据并发回给页面。
在Woeker中,全局对象是Worker对象本身,也就是说self,this这两个引用都指向Worker。Worker是一个最小化的环境,很多window对象的方法这里都没有,不过为了便于处理数据,在Worker中有这些对象和属性可用:
- 最小化的navigator对象,包括:onLine、appName、appVersion、userAgent、platform
- 只读的location
- setTimeout()、setInterval()、clearTimeout()、clearInterval()
- XMLHttpRequest构造函数
当在页面上通过postMessage将数据传递给Worker的时候,数据是以异步形式传递给Worker的。在Worker中,同样使用onmessage事件来接收页面发来的数据。处理完还是使用postMessage将数据发回给页面:
self.onmessage = function(event){
var data = event.data;
self.postMessage("Data from Worker:"+data.type+"&"+data.message);
};
在Worker内部,可以使用close()方法来终止脚本的运行。
包含其他脚本
在Worker中,因为不能访问DOM的原因,自然也不能创建script元素来添加并运行脚本。不过没关系,使用importScripts()方法可以引入外部代码,这个方法接收一个或多个指向JS文件的URL。每个加载过程都是异步执行的,在要引入的所有脚本下载完成后,这些脚本会在Woeker的作用域中按照参数的先后顺序执行。
importScripts("file1.js", "file2.js");