HTML5新兴API
随着 HTML5 的出现,面向未来 Web 应用的 JavaScript API 也得到了极大的发展。这些 API 没有包含在 HTML5 规范中,而是各自有各自的规范。
??但是,他们都属于“HTML5 相关的 API”。本节介绍的所有 API 都在持续制定中,还没有完全固定下来。
??无论如何,浏览器已经着手实现这些 API,而 Web 应用开发人员也都开始使用它们了。
??其中很多 API 都带有特定于浏览器的前缀,比如微软是 ms,而Chrome 和 Safari 是 webkit。通过添加这些前缀,不同的浏览器可以测试还在开发中的新 API,不过请记住,去掉前缀之后的部分在所有浏览器中都是一致的。
1、requestAnimationFrame()
??很长时间以来,计时器和循环间隔一直都是 JavaScript 动画的最核心技术。虽然 CSS 变换及动画为 Web 开发人员提供了实现动画的简单手段,但 JavaScript 动画开发领域的状况这些年来并没有大的变化。
??Firefox 4 最早为 JavaScript 动画添加了一个新 API,即 mozRequestAnimationFrame()。这个方法会告诉浏览器:有一个动画开始了。进而浏览器就可以确定重绘的最佳方式。
1.1、早期动画循环
??在 JavaScript 中创建动画的典型方式,就是使用 setInterval() 方法来控制所有动画。以下是一个使用 setInterval() 的基本动画循环:
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%"){
mozRequestAnimationFrame(updateProgress);
}
}
mozRequestAnimationFrame(updateProgress);
??因为 mozRequestAnimationFrame() 只运行一次传入的函数,因此在需要再次修改 UI 从而生成动画时,需要再次手工调用它。同样,也需要同时考虑什么时候停止动画。这样就能得到非常平滑流畅的动画。
??目前来看,mozRequestAnimationFrame() 解决了浏览器不知道 JavaScript 动画什么时候开始、不知道最佳循环间隔时间的问题,但不知道代码到底什么时候执行的问题呢?同样的方案也可以解决这个问题。
??我们传递的 mozRequestAnimationFrame() 函数也会接收一个参数,它是一个时间码(从 1970年 1 月 1 日起至今的毫秒数),表示下一次重绘的实际发生时间。注意,这一点很重要:mozRequestAnimationFrame() 会根据这个时间码设定将来的某个时刻进行重绘,而根据这个时间码,你也能知道那个时刻是什么时间。然后,再优化动画效果就有了依据。
??要知道距离上一次重绘已经过去了多长时间,可以查询mozAnimationStartTime,其中包含上一次重绘的时间码。用传入回调函数的时间码减去这个时间码,就能计算出在屏幕上重绘下一组变化之前要经过多长时间。使用这个值的典型方式如下:
(function(){
function draw(timestamp){
// 计算两次重绘的时间间隔
var drawStart = (timestamp || Date.now()),
diff = drawStart - startTime;
// 使用 diff 确定下一步的绘制时间
// 把 startTime 重写为这一次的绘制时间
startTime = drawStart;
// 重绘 UI
requestAnimationFrame(draw);
}
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame,
startTime = window.mozAnimationStartTime || Date.now();
requestAnimationFrame(draw);
})();
??以上模式利用已有的功能创建了一个动画循环,大致计算出了两次重绘的时间间隔。在 Firefox 中,计算时间间隔使用的是既有的时间码,而在 Chrome 和 IE 中,则使用不十分精确的 Date 对象。这个模
式可以大致体现出两次重绘的时间间隔,但不会告诉你在 Chrome 和 IE 中的时间间隔到底是多少。不过,大致知道时间间隔总比一点儿概念也没有好些。
??因为首先检测的是标准函数名,其次才是特定于浏览器的版本,所以这个动画循环在将来也能够使用。
??目前,W3C 已经着手起草 requestAnimationFrame() API,而且作为 Web Performance Group 的一部分,Mozilla 和 Google 正共同参与该标准草案的制定工作。
2、Page Visibility API
??不知道用户是不是正在与页面交互,这是困扰广大 Web 开发人员的一个主要问题。如果页面最小化了或者隐藏在了其他标签页后面,那么有些功能是可以停下来的,比如轮询服务器或者某些动画效果。
??而 Page Visibility API(页面可见性 API)就是为了让开发人员知道页面是否对用户可见而推出的。
??这个 API 本身非常简单,由以下三部分组成。
- document.hidden:表示页面是否隐藏的布尔值。页面隐藏包括页面在后台标签页中或者浏览器最小化。
- document.visibilityState:表示下列 4 个可能状态的值。
- 页面在后台标签页中或浏览器最小化。
- 页面在前台标签页中。
- 实际的页面已经隐藏,但用户可以看到页面的预览(就像在 Windows 7 中,用户把鼠标移动到任务栏的图标上,就可以显示浏览器中当前页面的预览)。
- 页面在屏幕外执行预渲染处理。
- visibilitychange 事件:当文档从可见变为不可见或从不可见变为可见时,触发该事件。
??在编写本书时,只有 IE10 和 Chrome 支持 Page Visibility API。IE 的版本是在每个属性或事件前面加上 ms 前缀,而 Chrome 则是加上 webkit 前缀。因此 document.hidden 在 IE 的实现中就是 document.msHidden,而在 Chrome 的实现中则是 document.webkitHidden。检查浏览器是否支持这个 API 的最佳方式如下:
if (document.hidden || document.msHidden || document.webKitHidden) {
// 页面隐藏了
} else {
// 页面未隐藏
}
??注意,以上代码在不支持该 API 的浏览器中会提示页面未隐藏。这是 Page Visibility API 有意设计的结果,目的是为了向后兼容。
??为了在页面从可见变为不可见或从不可见变为可见时收到通知,可以侦听 visibilitychange 事件。在 IE 中,这个事件叫 msvisibilitychange,而在 Chrome 中这个事件叫 webkitvisibilitychange。为了在两个浏览器中都能侦听到该事件,可以像下面的例子一样,为每个事件都指定相同的事件处理程序:
navigator.geolocation.getCurrentPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
});
??以上介绍的是成功回调函数。
??getCurrentPosition() 的第二个参数,即失败回调函数,在被调用的时候也会接收到一个参数。这个参数是一个对象,包含两个属性:message 和 code。
??其中,message 属性中保存着给人看的文本消息,解释为什么会出错,而 code 属性中保存着一个数值,表示错误的类型:
- 用户拒绝共享(1)
- 位置无效(2)
- 超时(3)
??实际开发中,大多数 Web 应用只会将错误消息保存到日志文件中,而不一定会因此修改用户界面。例如:
navigator.geolocation.getCurrentPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
}, function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message);
}, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 25000
});
??这三个选项都是可选的,可以单独设置,也可以与其他选项一起设置。除非确实需要非常精确的信息,否则建议保持 enableHighAccuracy 的 false 值(默认值)。将这个选项设置为 true 需要更长的时候,而且在移动设备上还会导致消耗更多电量。
??类似地,如果不需要频繁更新用户的位置信息,那么可以将 maximumAge 设置为 Infinity,从而始终都使用上一次的坐标信息。
??如果你希望跟踪用户的位置,那么可以使用另一个方法 watchPosition()。这个方法接收的参数与 getCurrentPosition() 方法完全相同。
??实际上, watchPosition() 与定时调用 getCurrentPosition() 的效果相同。在第一次调用 watchPosition() 方法后,会取得当前位置,执行成功回调或者错误回调。然后,watchPosition() 就地等待系统发出位置已改变的信号(它不会自己轮询位置)。
??调用 watchPosition() 会返回一个数值标识符,用于跟踪监控的操作。基于这个返回值可以取消监控操作,只要将其传递给 clearWatch() 方法即可(与使用 setTimeout() 和 clearTimeout() 类似)。例如:
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var files = EventUtil.getTarget(event).files,
i = 0,
len = files.length;
while (i < len){
console.log(files[i].name + " (" + files[i].type + ", " + files[i].size +
" bytes) ");
i++;
}
});
??这个例子把每个文件的信息输出到了控制台中。仅仅这一项功能,对 Web 应用开发来说就已经是非常大的进步了。
??不过,File API 的功能还不止于此,通过它提供的 FileReader 类型甚至还可以读取文件中的数据。
4.1、FileReader 类型
??FileReader 类型实现的是一种异步文件读取机制。可以把FileReader 想象成 XMLHttpRequest,区别只是它读取的是文件系统,而不是远程服务器。
??为了读取文件中的数据,FileReader 提供了如下几个方法。
- readAsText(file, encoding):以纯文本形式读取文件,将读取到的文本保存在 result 属性中。第二个参数用于指定编码类型,是可选的。
- readAsDataURL(file):读取文件并将文件以数据 URI 的形式保存在 result 属性中。
- readAsBinaryString(file):读取文件并将一个字符串保存在 result 属性中,字符串中的每个字符表示一字节。
- readAsArrayBuffer(file):读取文件并将一个包含文件内容的 ArrayBuffer 保存在 result 属性中。
??这些读取文件的方法为灵活地处理文件数据提供了极大便利。例如,可以读取图像文件并将其保存为数据 URI,以便将其显示给用户,或者为了解析方便,可以将文件读取为文本形式。
??由于读取过程是异步的,因此 FileReader 也提供了几个事件。其中最有用的三个事件是 progress、error 和 load,分别表示是否又读取了新数据、是否发生了错误以及是否已经读完了整个文件。
??每过 50ms 左右,就会触发一次 progress 事件,通过事件对象可以获得与 XHR 的 progress 事件相同的信息(属性):lengthComputable、loaded 和 total。
??另外,尽管可能没有包含全部数据,但每次 progress 事件中都可以通过 FileReader 的 result 属性读取到文件内容。
??由于种种原因无法读取文件,就会触发 error 事件。触发 error 事件时,相关的信息将保存到 FileReader 的 error 属性中。这个属性中将保存一个对象,该对象只有一个属性 code,即错误码。
??这个错误码
- 1 表示未找到文件,
- 2 表示安全性错误,
- 3 表示读取中断,
- 4 表示文件不可读,
- 5 表示编码错误。
??文件成功加载后会触发 load 事件;如果发生了 error 事件,就不会发生 load 事件。以下是一个使用上述三个事件的例子。
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;
}
}
??Blob 类型有一个 size 属性和一个 type 属性,而且它也支持 slice() 方法,以便进一步切割数据。
??通过 FileReader 也可以从 Blob 中读取数据。下面这个例子只读取文件的 32B 内容。
function createObjectURL(blob){
if (window.URL){
return window.URL.createObjectURL(blob);
} else if (window.webkitURL){
return window.webkitURL.createObjectURL(blob);
} else {
return null;
}
}
??这个函数的返回值是一个字符串,指向一块内存的地址。因为这个字符串是 URL,所以在 DOM 中也能使用。例如,以下代码可以在页面中显示一个图像文件:
function revokeObjectURL(url){
if (window.URL){
window.URL.revokeObjectURL(url);
} else if (window.webkitURL){
window.webkitURL.revokeObjectURL(url);
}
}
??页面卸载时会自动释放对象 URL 占用的内存。不过,为了确保尽可能少地占用内存,最好在不需要某个对象 URL 时,就马上手工释放其占用的内存。
??支持对象 URL 的浏览器有 IE10+、Firefox 4 和 Chrome。
4.4、读取拖放文件
??围绕读取文件信息,结合使用 HTML5 拖放 API 和文件 API,能够创造出令人瞩目的用户界面:在页面上创建了自定义的放置目标之后,你可以从桌面上把文件拖放到该目标。与拖放一张图片或者一个链接类似,从桌面上把文件拖放到浏览器中也会触发 drop 事件。而且可以在 event.dataTransfer. files 中读取到被放置的文件,当然此时它是一个 File 对象,与通过文件输入字段取得的 File 对象一样。
??下面这个例子会将放置到页面中自定义的放置目标中的文件信息显示出来:
var droptarget = document.getElementById("droptarget");
function handleEvent(event){
var info = "",
output = document.getElementById("output"),
data, xhr,
files, i, len;
EventUtil.preventDefault(event);
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", "FileAPIExample06Upload.php", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
alert(xhr.responseText);
}
};
xhr.send(data);
}
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
??这个例子创建一个 FormData 对象,与每个文件对应的键分别是 file0、file1、file2 这样的格式。注意,不用额外写任何代码,这些文件就可以作为表单的值提交。而且,也不必使用 FileReader,只要传入 File 对象即可。
??使用 FormData 上传文件,在服务器端就好像是接收到了常规的表单数据一样,一切按部就班地处理即可。
??换句话说,如果服务器端使用的是 PHP,那么$_FILES 数组中就会保存着上传的文件。支持以这种方式上传文件的浏览器有 Firefox 4+、Safari 5+和 Chrome。
5、Web 计时
??页面性能一直都是 Web 开发人员最关注的领域。但直到最近,度量页面性能指标的唯一方式,就是提高代码复杂程度和巧妙地使用 JavaScript 的 Date 对象。
??Web Timing API 改变了这个局面,让开发人员通过 JavaScript 就能使用浏览器内部的度量结果,通过直接读取这些信息可以做任何想做的分析。
??与本章介绍过的其他 API 不同,Web Timing API 实际上已经成为了 W3C 的建议标准,只不过目前支持它的浏览器还不够多。
??Web 计时机制的核心是 window.performance 对象。对页面的所有度量信息,包括那些规范中已经定义的和将来才能确定的,都包含在这个对象里面。
??Web Timing 规范一开始就为 performance 对象定义了两个属性。
??其中,performance.navigation 属性也是一个对象,包含着与页面导航有关的多个属性,如下所示。
- redirectCount:页面加载前的重定向次数。
- type:数值常量,表示刚刚发生的导航类型。
- performance.navigation.TYPE_NAVIGATE (0):页面第一次加载。
- performance.navigation.TYPE_RELOAD (1):页面重载过。
- performance.navigation.TYPE_BACK_FORWARD (2):页面是通过“后退”或“前进”按钮打开的。
??另外,performance.timing 属性也是一个对象,但这个对象的属性都是时间戳(从软件纪元开始经过的毫秒数),不同的事件会产生不同的时间值。这些属性如下所示。
- navigationStart:开始导航到当前页面的时间。
- unloadEventStart:前一个页面的 unload 事件开始的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为 0。
- unloadEventEnd:前一个页面的 unload 事件结束的时间。但只有在前一个页面与当前页面来自同一个域时这个属性才会有值;否则,值为 0。
- redirectStart:到当前页面的重定向开始的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为 0。
- redirectEnd:到当前页面的重定向结束的时间。但只有在重定向的页面来自同一个域时这个属性才会有值;否则,值为 0。
- fetchStart:开始通过 HTTP GET 取得页面的时间。
- domainLookupStart:开始查询当前页面 DNS 的时间。
- domainLookupEnd:查询当前页面 DNS 结束的时间。
- connectStart:浏览器尝试连接服务器的时间。
- connectEnd:浏览器成功连接到服务器的时间。
- secureConnectionStart:浏览器尝试以 SSL 方式连接服务器的时间。不使用 SSL 方式连接时,这个属性的值为 0。
- requestStart:浏览器开始请求页面的时间。
- responseStart:浏览器接收到页面第一字节的时间。
- responseEnd:浏览器接收到页面所有内容的时间。
- domLoading:document.readyState 变为"loading"的时间。
- domInteractive:document.readyState 变为"interactive"的时间。
- domContentLoadedEventStart:发生 DOMContentLoaded 事件的时间。
- domContentLoadedEventEnd:DOMContentLoaded 事件已经发生且执行完所有事件处理程序的时间。
- domComplete:document.readyState 变为"complete"的时间。
- loadEventStart:发生 load 事件的时间。
- loadEventEnd:load 事件已经发生且执行完所有事件处理程序的时间。
??通过这些时间值,就可以全面了解页面在被加载到浏览器的过程中都经历了哪些阶段,而哪些阶段可能是影响性能的瓶颈。给大家推荐一个使用 Web Timing API 的绝好示例,地址是 http://webtimingdemo.appspot.com/。
??支持 Web Timing API 的浏览器有 IE10+和 Chrome。
6、Web Workers
??随着 Web 应用复杂性的与日俱增,越来越复杂的计算在所难免。长时间运行的 JavaScript 进程会导致浏览器冻结用户界面,让人感觉屏幕“冻结”了。
??Web Workers 规范通过让 JavaScript 在后台运行解决了这个问题。
??浏览器实现 Web Workers 规范的方式有很多种,可以使用线程、后台进程或者运行在其他处理器核心上的进程,等等。具体的实现细节其实没有那么重要,重要的是开发人员现在可以放心地运行 JavaScript,而不必担心会影响用户体验了。
??目前支持 Web Workers 的浏览器有 IE10+、Firefox 3.5+、Safari 4+、Opera 10.6+、Chrome 和 iOS 版的 Safari。
6.1、使用 Worker
??实例化 Worker 对象并传入要执行的 JavaScript 文件名就可以创建一个新的 Web Worker。例如:
worker.postMessage(“start! ");
??消息内容可以是任何能够被序列化的值,不过与 XDM 不同的是,在所有支持的浏览器中,postMessage() 都能接收对象参数(Safari 4 是支持 Web Workers 的浏览器中最后一个只支持字符串参数的)。因此,可以随便传递任何形式的对象数据,如下面的例子所示:
worker.onmessage = function(event){
var data = event.data;
// 对数据进行处理
}
??Worker 不能完成给定的任务时会触发 error 事件。具体来说,Worker 内部的 JavaScript 在执行过程中只要遇到错误,就会触发 error 事件。
??发生 error 事件时,事件对象中包含三个属性:filename、lineno 和 message,分别表示发生错误的文件名、代码行号和完整的错误消息。
worker.terminate(); // 立即停止 Worker 的工作
6.2、Worker 全局作用域
??关于 Web Worker,最重要的是要知道它所执行的 JavaScript 代码完全在另一个作用域中,与当前网页中的代码不共享作用域。
??在 Web Worker 中,同样有一个全局对象和其他对象以及方法。但是,Web Worker 中的代码不能访问 DOM,也无法通过任何方式影响页面的外观。
??Web Worker 中的全局对象是 worker 对象本身。也就是说,在这个特殊的全局作用域中,this 和 self 引用的都是 worker 对象。为便于处理数据,Web Worker 本身也是一个最小化的运行环境。
- 最小化的 navigator 对象,包括 onLine、appName、appVersion、userAgent 和 platform 属性;
- 只读的 location 对象;
- setTimeout()、setInterval()、clearTimeout() 和 clearInterval() 方法;
- XMLHttpRequest 构造函数。
??显然,Web Worker 的运行环境与页面环境相比,功能是相当有限的。
??当页面在 worker 对象上调用 postMessage() 时,数据会以异步方式被传递给 worker,进而触发 worker 中的 message 事件。为了处理来自页面的数据,同样也需要创建一个 onmessage 事件处理程序。
// Web Worker 内部的代码
self.onmessage = function(event){
var data = event.data;
// 别忘了,默认的 sort() 方法只比较字符串
data.sort(function(a, b){
return a – b;
});
self.postMessage(data);
};
??传递消息就是页面与 Worker 相互之间通信的方式。在 Worker 中调用 postMessage() 会以异步方式触发页面中 Worker 实例的 message 事件。如果页面想要使用这个 Worker,可以这样:
// Web Worker 内部的代码
self.close();
6.3、包含其他脚本
??既然无法在 Worker 中动态创建新的<script>元素,那是不是就不能向 Worker 中添加其他脚本了呢?
??不是,Worker 的全局作用域提供这个功能,即我们可以调用 importScripts() 方法。这个方法接收一个或多个指向 JavaScript 文件的 URL。每个加载过程都是异步进行的,因此所有脚本加载并执行之后,importScripts() 才会执行。例如:
// Web Worker 内部的代码
importScripts("file1.js", "file2.js");
??即使 file2.js 先于 file1.js 下载完,执行的时候仍然会按照先后顺序执行。而且,这些脚本是在 Worker 的全局作用域中执行,如果脚本中包含与页面有关的 JavaScript 代码,那么脚本可能无法正确运行。请记住,Worker 中的脚本一般都具有特殊的用途,不会像页面中的脚本那么功能宽泛。
6.4、Web Workers 的未来
??Web Workers 规范还在继续制定和改进之中。本节所讨论的 Worker 目前被称为“专用 Worker”(dedicated worker),因为它们是专门为某个特定的页面服务的,不能在页面间共享。
??该规范的另外一个概念是“共享 Worker”(shared worker),这种 Worker 可以在浏览器的多个标签中打开的同一个页面间共享。虽然 Safari 5、Chrome 和 Opera 10.6 都实现了共享 Worker,但由于该规范尚未完稿,因此很可能还会有变动。
??另外,关于在 Worker 内部能访问什么不能访问什么,到如今仍然争论不休。有人认为 Worker 应该像页面一样能够访问任意数据,不光是 XHR,还有 localStorage、sessionStorage、Indexed DB、Web Sockets、Server-Send Events 等。好像支持这个观点的人更多一些,因此未来的 Worker 全局作用域很可能会有更大的空间。
小结
??与 HTML5 同时兴起的是另外一批 JavaScript API。从技术规范角度讲,这批 API 不属于 HTML5,但从整体上可以称它们为 HTML5 JavaScript API。这些 API 的标准有不少虽然还在制定当中,但已经得到了浏览器的广泛支持,因此本章重点讨论了它们。
- requestAnimationFrame():是一个着眼于优化 JavaScript 动画的 API,能够在动画运行期间发出信号。通过这种机制,浏览器就能够自动优化屏幕重绘操作。
- Page Visibility API:让开发人员知道用户什么时候正在看着页面,而什么时候页面是隐藏的。
- Geolocation API:在得到许可的情况下,可以确定用户所在的位置。在移动 Web 应用中,这个 API 非常重要而且常用。
- File API:可以读取文件内容,用于显示、处理和上传。与 HTML5 的拖放功能结合,很容易就能创造出拖放上传功能。
- Web Timing:给出了页面加载和渲染过程的很多信息,对性能优化非常有价值。
- Web Workers:可以运行异步 JavaScript 代码,避免阻塞用户界面。在执行复杂计算和数据处理的时候,这个 API 非常有用;要不然,这些任务轻则会占用很长时间,重则会导致用户无法与页面交互。
原文地址
https://www.jianshu.com/p/edeff1860328