尽管现在的网络环境变得越来越好,但离线访问对于网站来说依然非常重要。一个好的离线访问能够有效减少服务器的压力、能够节约用户的网络带宽等。
今天我们来看看如何与HTML5技术一起实现一个好用的离线浏览。
本文翻译自http://www.html5rocks.com/en/tutorials/appcache/beginner/,如果您对其中的任何技术细节有所疑问,请以原文为准。
manifest文件
manifest文件是一个普通的文本文件,它将指引浏览器缓存哪些文件。
引用
如果要在自己的网页中使用离线缓存,就需要像这样使用:
...
manifest
属性是网页缓存的标志,因此,每一个访问时需要缓存的网页,其html标签当中都应该存有这一属性;一旦浏览器没有找到这个属性,那么浏览器就会向服务器请求数据,而如果用户此时恰好没有网络连接,那么离线体验就将立即中断。同时,每当用户访问一个带有manifest属性的页面时,manifest文件当中的缓存项目都将被加入到浏览器的缓存当中,因此我们不需要在一个manifest文件当中就写齐所有的项目。
manifest文件的引用和图片、链接等较为类似,相对路径和绝对路径都可以,但绝对路径中,manifest文件的位置必须与网站在同一个域下。另外,manifest文件的扩展名并没有硬性规定,但其MIME类型必须写作:text/cache-manifest
。这一点需要注意,因为你的服务器可能并没有针对这一类型进行设置,比如在Apache当中,我们就得如下增加一个类型:
AddType text/cache-manifest .appcache
注意,.appcache
是我们自定义的类型扩展名,你需要按照你自己的扩展名书写。
manifest结构
manifest文件的内容长短不一,最简单的就是像下面这样:
CACHE MANIFEST index.html stylesheet.css images/logo.png scripts/main.js
在这段样例中,一共有4个文件会被缓存。注意:
- 第一行的
CACHE MANIFEST
字样必须写出 - 通常情况下,离线缓存仅仅只有5MB左右。但是如果你写的是Google的Chrome Web Store中的应用,则可以使用
unlimitedStorage
消除离线缓存的大小限制 - 如果缓存文件当中有一项请求失败或者下载失败,那么整个缓存过程就会终止,浏览器将继续使用上次的缓存
现在我们来看一个更复杂的manifest文件:
CACHE MANIFEST # 2010-06-18:v2 # Explicitly cached 'master entries'. CACHE: /favicon.ico index.html stylesheet.css images/logo.png scripts/main.js # Resources that require the user to be online. NETWORK: login.php /myapi http://api.twitter.com # static.html will be served if main.py is inaccessible # offline.jpg will be served in place of all images in images/large/ # offline.html will be served in place of all other .html files FALLBACK: /main.py /static.html images/large/ images/offline.jpg *.html /offline.html
首先,以#
开头的行,都会被浏览器以注释的形式无视掉。其次,很重要的一点,就是一旦一个文件被缓存下来,那么即便它在服务器上更新了,浏览器也不会主动刷新缓存;因此我们必须主动地刷新这份manifest文件的内容来强制刷新。
下面,我们来看看manifest文件当中的几个节。
CACHE
。CACHE节是manifest文件当中的默认节,如果我们在文件当中不写出任何节的名称,那么浏览器就会默认将所有内容都认为是CACHE节。这个节的意义就是告诉浏览器,以下的文件属于缓存内容。因此,其下的文件只会下载一次。NETWORK
。NETWORK节相当于一个白名单,任何写入这个节的文件都将在页面刷新时重新请求,即便用户处于离线状态,浏览器也会去尝试获取。这当中允许使用通配符。FALLBACK
。FALLBACK节是一个回避机制的体现。在这个节中,每一行都需要有两个URI,其中的第一个会被首先请求,一旦无法请求得到,浏览器便会转而请求第二个作为回避。但是,这两个URI都必须写为相对路径,其相对参照即是manifest文件本身。
这三个节的顺序是任意的,你可以打乱它们的顺序。
下面我们来看看另一个实例:
CACHE MANIFEST # 2010-06-18:v3 # Explicitly cached entries index.html css/style.css # offline.html will be displayed if the user is offline FALLBACK: / /offline.html # All other resources (e.g. sites) require the user to be online. NETWORK: * # Additional resources to cache CACHE: images/logo1.png images/logo2.png images/logo3.png
在这个例子中,除了少数几个logo文件之外,所有的资源都不被缓存,同时,一旦用户在离线情况下访问网站根目录,offline.html就会显示。注意,引用manifest文件的网页HTML默认会被缓存,而无需再写入manifest文件。
更新缓存
当用户处于缓存模式时,发生如下的情况将会重新连接网络:
- 用户主动清理了浏览器的缓存
- manifest文件本身发生改动
- 缓存通过编程更新
缓存状态
浏览器为开发者提供了一个window.applicationCache
对象,我们通过对该对象的status
属性,可以获知缓存的状态:
var appCache = window.applicationCache; switch (appCache.status) { case appCache.UNCACHED: // UNCACHED == 0 return 'UNCACHED'; break; case appCache.IDLE: // IDLE == 1 return 'IDLE'; break; case appCache.CHECKING: // CHECKING == 2 return 'CHECKING'; break; case appCache.DOWNLOADING: // DOWNLOADING == 3 return 'DOWNLOADING'; break; case appCache.UPDATEREADY: // UPDATEREADY == 4 return 'UPDATEREADY'; break; case appCache.OBSOLETE: // OBSOLETE == 5 return 'OBSOLETE'; break; default: return 'UKNOWN CACHE STATUS'; break; };
如果要通过编程手段来更新缓存,首先应当使用applicationCache.update()
来迫使浏览器重新下载一份manifest文件。这时如果我们放在服务器上的manifest文件已经发生变化,那么applicationCache.status
的值就会变成UPDATEREADY
,此时如果再使用applicationCache.swapCache()
即可更新缓存;而如果这份文件本身并没有发生变化,那么缓存并不会更新。
var appCache = window.applicationCache; appCache.update(); // Attempt to update the user's cache. ... if (appCache.status == window.applicationCache.UPDATEREADY) { appCache.swapCache(); // The fetch was successful, swap in the new cache. }
通常,我们通过增加一个特定的事件监听来完成这一过程:
// Check if a new cache is available on page load. window.addEventListener('load', function(e) { window.applicationCache.addEventListener('updateready', function(e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { // Browser downloaded a new app cache. // Swap it in and reload the page to get the new hotness. window.applicationCache.swapCache(); if (confirm('A new version of this site is available. Load it?')) { window.location.reload(); } } else { // Manifest didn't changed. Nothing new to server. } }, false); }, false);
缓存事件
浏览器还另外提供了有关缓存的几个事件:
function handleCacheEvent(e) { //... } function handleCacheError(e) { alert('Error: Cache failed to update!'); }; // 当manifest文件第一次开始实施缓存时触发 appCache.addEventListener('cached', handleCacheEvent, false); // 当检查manifest文件时触发 appCache.addEventListener('checking', handleCacheEvent, false); // 当manifest文件发生变化后,资源重新抓取时触发 appCache.addEventListener('downloading', handleCacheEvent, false); // manifest文件请求返回404或410时触发 appCache.addEventListener('error', handleCacheError, false); // 第一次下载manifest文件之后触发 appCache.addEventListener('noupdate', handleCacheEvent, false); // 当manifest文件请求返回404或410时触发,且会导致浏览器清理缓存 appCache.addEventListener('obsolete', handleCacheEvent, false); // 每当manifest当中的缓存项目执行一项时触发一次 appCache.addEventListener('progress', handleCacheEvent, false); // 缓存刷新完成时触发 appCache.addEventListener('updateready', handleCacheEvent, false);
最后,提供一份API文档作为参考:http://www.whatwg.org/specs/web-apps/current-work/#applicationcache。