尽管现在的网络环境变得越来越好,但离线访问对于网站来说依然非常重要。一个好的离线访问能够有效减少服务器的压力、能够节约用户的网络带宽等。

今天我们来看看如何与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

About liuyanghejerry

富有激情的前端工程师,专注GUI开发。

Comments are closed.

Post Navigation