HTML5 离线缓存(Application Cache)
HTML5 离线缓存(Application Cache)
Introduction
HTML5 Application Cache,通常称为 AppCache,是HTML5规范中引入的一项强大功能,它允许Web开发者指定浏览器缓存特定的网页资源(HTML、CSS、JavaScript、图片等),使得用户在离线状态(没有网络连接)时依然能够访问这些被缓存的页面。这项技术彻底改变了Web应用的使用体验——一旦资源被缓存,用户再次访问时可以瞬间加载页面,无需等待网络请求,真正实现了"安装级"的访问体验。
AppCache的核心思想是通过一个清单文件(Manifest File)来声明哪些资源需要被浏览器缓存。这个清单文件列出了所有需要缓存的资源路径,浏览器在首次加载页面时会根据清单下载并存储这些资源。此后,即使网络中断,被缓存的资源依然可以正常加载。
AppCache在单页应用(SPA)、 Progressive Web App(PWA)概念出现之前,是实现离线Web体验的主要技术方案。尽管后来被 Service Workers 逐渐取代(因为Service Workers提供了更灵活、更强大的控制能力),但理解AppCache的原理和用法,对于理解Web离线存储技术体系仍然具有重要的学习价值。本文将完整介绍AppCache的使用方法、注意事项以及它的演进方向。
基础语法
Manifest文件结构
AppCache的核心是一个扩展名为 .appcache 的纯文本文件(通常命名为 manifest.appcache 或 offline.appcache),需要在HTML标签中通过 manifest 属性来引用:
<!DOCTYPE html>
<html manifest="manifest.appcache">
<head>
<meta charset="UTF-8">
<title>我的离线应用</title>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
重要:引用manifest文件的HTML页面本身会被自动缓存。
Manifest文件的格式
清单文件由三部分组成: <code>CACHE MANIFEST</code> 头、各资源路径和节头(CACHE:、NETWORK:、FALLBACK:)。
基本结构如下:
CACHE MANIFEST
# version 1.0.0
# 注释行以 # 开头,常用于版本控制
CACHE:
index.html
style.css
app.js
images/logo.png
images/bg.jpg
NETWORK:
/api/*
login.html
FALLBACK:
offline.html
三个节(Section)的含义
CACHE: — 显式缓存的资源列表。这些资源会在首次下载时被缓存,之后即使网络可用也会从缓存加载。如果省略节头,资源路径默认在CACHE节。
NETWORK: — 只能从网络获取的资源(白名单)。这些资源不会被缓存,每次访问都会尝试从服务器加载。通常用于API接口、登录页面等动态内容。星号 * 表示所有不在CACHE中的资源都从网络获取。
FALLBACK: — 后备资源。当网络请求失败时,返回的替代资源。格式为 原资源路径 备用资源路径,注意两个路径之间有空格分隔。
代码示例
示例一:完整的离线博客
#### 1. 创建HTML页面
<!DOCTYPE html>
<html lang="zh-CN" manifest="blog.appcache">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的技术博客</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>我的技术博客</h1>
<nav>
<a href="index.html">首页</a>
<a href="articles.html">文章</a>
<a href="about.html">关于</a>
</nav>
</header>
<main>
<article>
<h2>欢迎访问我的博客</h2>
<p>这是一个支持离线访问的博客示例。</p>
</article>
</main>
<footer>
<p>© 2024 技术博客</p>
</footer>
<script src="app.js"></script>
</body>
</html>
#### 2. 创建CSS样式
/* style.css */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
header {
border-bottom: 2px solid #333;
padding-bottom: 10px;
margin-bottom: 20px;
}
nav a {
margin-right: 15px;
text-decoration: none;
color: #0066cc;
}
article {
background: #f9f9f9;
padding: 15px;
border-radius: 5px;
}
#### 3. 创建JavaScript(带状态提示)
// app.js
function updateOnlineStatus() {
const status = navigator.onLine ? '在线' : '离线';
const indicator = document.getElementById('status-indicator');
if (indicator) {
indicator.textContent = `当前状态: ${status}`;
indicator.style.color = navigator.onLine ? 'green' : 'red';
}
console.log(`应用状态: ${status}`);
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
// 初始化状态显示
document.addEventListener('DOMContentLoaded', () => {
updateOnlineStatus();
});
// 检查AppCache事件
if ('applicationCache' in navigator) {
const cache = applicationCache;
cache.addEventListener('checking', () => {
console.log('正在检查清单文件...');
});
cache.addEventListener('cached', () => {
console.log('资源已缓存完成,可以离线使用');
});
cache.addEventListener('downloading', () => {
console.log('正在下载新资源...');
});
cache.addEventListener('updateready', () => {
console.log('有新版本可用,刷新页面以加载新版本');
if (confirm('检测到新版本,是否刷新页面?')) {
cache.update();
location.reload();
}
});
cache.addEventListener('error', (e) => {
console.error('AppCache错误:', e);
});
}
#### 4. 创建Manifest清单文件
CACHE MANIFEST
# 版本: 1.0.3
# 更新日期: 2024-01-15
CACHE:
index.html
style.css
app.js
images/logo.png
NETWORK:
/api/*
https://fonts.googleapis.com/*
https://fonts.gstatic.com/*
FALLBACK:
images/default.png images/placeholder.png
示例二:带版本管理的Manifest
<?php
// manifest.php - 动态生成带版本号的manifest文件
header('Content-Type: text/cache-manifest');
$version = isset($_GET['v']) ? $_GET['v'] : date('YmdHis');
?>
CACHE MANIFEST
# 版本: <?= $version ?>
CACHE:
/
style.css
app.js
offline.jpg
NETWORK:
*
在Nginx中为manifest文件添加正确的MIME类型:
location ~ \.appcache$ {
add_header Content-Type text/cache-manifest;
}
示例三:API请求的FALLBACK策略
CACHE MANIFEST
# API后备策略
CACHE:
index.html
style.css
NETWORK:
/api/*
FALLBACK:
/api/userinfo.json cached-userinfo.json
/api/products.json cached-products.json
当 /api/userinfo.json 请求失败时,自动返回 cached-userinfo.json。
运行效果
首次访问(联网状态)
1. 浏览器解析HTML,发现 <html manifest="blog.appcache">
2. 浏览器请求并解析 blog.appcache 清单文件
3. 浏览器触发 checking 事件,然后开始下载清单中列出的所有资源
4. 下载过程中触发 downloading 事件(可显示进度条)
5. 所有资源下载完成后,触发 cached 事件
6. 页面正常加载,用户可以正常浏览
后续访问(离线状态)
1. 用户断开网络(或开启飞行模式)
2. 用户再次访问同一页面
3. 浏览器检测到应用缓存,从缓存中加载所有资源
4. 页面秒开,无需等待网络加载
5. JavaScript中的 navigator.onLine 检测到离线状态,可以显示"离线模式"提示
有更新时的行为
1. 用户再次联网访问时,浏览器重新检查 manifest.appcache
2. 如果清单文件有变化(哪怕只是注释中的版本号变了),浏览器触发 downloading 事件并开始下载新版本资源
3. 新资源在后台下载,但不会立即替换旧缓存
4. 下载完成后,浏览器触发 updateready 事件
5. 此时用户看到的仍是旧页面,需要刷新页面才能看到新内容
6. 或者在JS中调用 applicationCache.update() + location.reload() 强制刷新
在Chrome DevTools中查看
打开Chrome开发者工具 → Application(Application)面板 → Application Cache,可以查看:
- 缓存的资源列表及大小
- 当前的缓存状态
- 网络请求中哪些来自缓存、哪些来自网络
常见问题
Q1:Manifest文件不生效
问题描述:配置了manifest属性但离线时页面无法加载。
排查步骤:
1. 确认manifest文件存在且路径正确(相对于HTML文件)
2. 确认manifest文件格式完全正确,第一行必须是 CACHE MANIFEST(注意空格)
3. 确认服务器正确返回了manifest文件的MIME类型为 text/cache-manifest
4. 在Nginx中需要手动添加:
location ~ \.appcache$ {
add_header Content-Type text/cache-manifest;
}
5. 确认manifest文件没有语法错误,所有资源路径都是真实存在的
6. 跨域的manifest文件不被支持——manifest必须与页面同源
Q2:缓存不更新(用户看到旧版本)
问题描述:更新了服务器上的资源,但用户刷新后看到仍是旧版本。
原因:这是AppCache的"静默缓存"机制导致的。AppCache的设计哲学是:一旦缓存,永不改变,直到manifest文件本身发生变化。
解决方案:
1. 修改manifest文件(最可靠的方式):
- 更改注释中的版本号
- 添加或删除一个空行(改变文件hash)
- 任何对manifest文件的修改都会触发浏览器重新下载所有资源
2. 在JavaScript中监听
updateready 事件并提示用户刷新: applicationCache.addEventListener('updateready', () => {
if (confirm('有新版本可用,是否立即更新?')) {
location.reload();
}
});
3. 用户也可以手动清除浏览器缓存或使用开发者工具强制刷新(Ctrl+Shift+R)
Q3:更新顺序问题导致页面错乱
问题描述:更新资源后,页面加载出现样式错乱或脚本错误。
原因:当manifest更新时,浏览器可能正在加载旧版本的资源。如果manifest列出的文件之间存在依赖关系(如index.html依赖v2版本的app.js),而服务器上文件尚未完全更新,可能导致不一致。
最佳实践:
- 采用"原子更新"策略,即所有资源文件同步更新,然后最后更新manifest文件
- 或者使用版本化文件名策略(<code>app.v1.js</code>、<code>app.v2.js</code>),避免依赖冲突
- 建议采用增量更新更好的Service Workers替代方案
Q4:HTTPS要求
问题描述:在本地开发环境(http://localhost)可以正常使用,但部署到生产环境后AppCache不工作。
原因:现代浏览器(Chrome 50+、Firefox 44+等)出于安全考虑,要求AppCache必须运行在HTTPS协议下(或 localhost)。
解决方案:
- 确保生产环境使用HTTPS(Let's Encrypt提供免费SSL证书)
- 如果必须使用HTTP,考虑迁移到Service Workers(Service Workers也要求HTTPS,但控制更精细)
- 使用localhost进行开发测试
Q5:跨域资源无法缓存
问题描述:清单中引用的跨域资源没有被缓存。
原因:出于安全限制,AppCache不支持跨域的manifest文件。即使通过 Access-Control-Allow-Origin 允许跨域访问,浏览器也不会缓存跨域资源。
解决方案:
- 在NETWORK节中列出跨域资源,声明它们需要联网获取
- 对于字体、图片等静态资源,考虑将它们复制到同域服务器上
- 对于API数据,考虑使用 <code>localStorage</code> 或 <code>IndexedDB</code> 做本地缓存
- <strong>推荐方案</strong>:迁移到Service Workers,它支持更灵活的跨域资源处理
Q6:设备存储空间不足
问题描述:在移动设备上提示缓存存储空间不足。
原因:不同浏览器和设备对AppCache的存储配额有不同的限制,通常在50MB左右。
解决方案:
- 只缓存必要的核心资源,不要缓存大文件(视频、大型图片库等)
- 使用 <code>applicationCache.swapCache()</code> 手动管理缓存替换逻辑
- 对于大型应用,考虑使用 IndexedDB 替代 AppCache 做数据存储
- 迁移到Service Workers + Cache API 的组合
延伸阅读
1. MDN — DOM/Window/applicationCache — AppCache API文档 — 最权威的AppCache接口文档,包含所有事件和方法的详细说明。
2. MDN — 使用应用缓存 — Application Cache API — 完整的AppCache使用指南,包含详细的清单文件示例和注意事项。
3. HTML5 Rocks — 离线入门 — Offline First: The Art of Building applications that work without a network — 深入讲解离线Web应用的设计理念和实践方法。
4. Google Web Fundamentals — Service Workers — Service Workers: an Offiline Playbook — AppCache的现代替代方案,Service Workers提供了更强大的离线能力,强烈建议学习。
5. Can I Use — AppCache — Browser Compatibility: AppCache — 查询各浏览器对AppCache的支持情况及已知问题。
重要提醒:AppCache已被官方标记为Deprecated(废弃)状态,主流浏览器虽然仍支持,但新的Web项目建议直接使用 Service Workers + Cache API 来实现离线功能。Service Workers提供了声明式缓存策略、细粒度的缓存控制、后台同步等更强大的能力,是Web离线技术的未来方向。不过,学习AppCache有助于理解离线Web应用的基本原理,为学习Service Workers打下基础。