HTML5新特性与离线存储深度解析

一、HTML5新特性全面解析

1.1 语义化标签革命

HTML5引入的语义化标签彻底改变了网页结构的设计方式,让代码更具可读性和可访问性。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5语义化示例</title>
    <style>
        /* 语义化标签的默认样式重置 */
        header, nav, main, article, section, aside, footer, figure, figcaption {
            display: block;
            margin: 0;
            padding: 0;
        }
        
        .site-header {
            background: #2c3e50;
            color: white;
            padding: 1rem;
        }
        
        .main-navigation {
            background: #34495e;
            padding: 0.5rem;
        }
        
        .content-area {
            display: grid;
            grid-template-columns: 3fr 1fr;
            gap: 2rem;
            padding: 2rem;
        }
        
        .article-card {
            border: 1px solid #bdc3c7;
            border-radius: 8px;
            padding: 1.5rem;
            margin-bottom: 1.5rem;
        }
        
        .sidebar {
            background: #ecf0f1;
            padding: 1.5rem;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <!-- 页眉区域 -->
    <header class="site-header" role="banner">
        <h1>我的技术博客</h1>
        <p>分享前端开发知识与经验</p>
    </header>

    <!-- 导航区域 -->
    <nav class="main-navigation" role="navigation" aria-label="主导航">
        <ul>
            <li><a href="#home">首页</a></li>
            <li><a href="#articles">文章</a></li>
            <li><a href="#tutorials">教程</a></li>
            <li><a href="#about">关于</a></li>
        </ul>
    </nav>

    <!-- 主要内容区域 -->
    <main class="content-area" role="main">
        <!-- 文章内容 -->
        <article class="main-content">
            <header>
                <h2>HTML5语义化标签详解</h2>
                <time datetime="2024-01-15">2024年1月15日</time>
                <address>作者: 技术达人</address>
            </header>

            <section>
                <h3>什么是语义化标签</h3>
                <p>语义化标签是指使用具有明确含义的HTML标签来描述内容的结构...</p>
                
                <figure>
                    <img src="semantic-html.jpg" alt="HTML5语义化结构示意图">
                    <figcaption>图1: HTML5文档结构示意图</figcaption>
                </figure>
            </section>

            <section>
                <h3>语义化标签的优势</h3>
                <ul>
                    <li>更好的可访问性</li>
                    <li>改进的SEO效果</li>
                    <li>更清晰的代码结构</li>
                    <li>更好的维护性</li>
                </ul>
            </section>

            <footer>
                <p>标签: <mark>HTML5</mark>, <mark>语义化</mark>, <mark>前端开发</mark></p>
            </footer>
        </article>

        <!-- 侧边栏 -->
        <aside class="sidebar" role="complementary" aria-label="相关链接">
            <section>
                <h4>相关文章</h4>
                <ul>
                    <li><a href="#">CSS Grid布局完全指南</a></li>
                    <li><a href="#">JavaScript ES6+ 新特性</a></li>
                </ul>
            </section>

            <section>
                <h4>热门标签</h4>
                <div>
                    <span class="tag">Vue.js</span>
                    <span class="tag">React</span>
                    <span class="tag">TypeScript</span>
                </div>
            </section>
        </aside>
    </main>

    <!-- 页脚 -->
    <footer class="site-footer" role="contentinfo">
        <p>&copy; 2024 我的技术博客. 保留所有权利.</p>
        <nav aria-label="页脚导航">
            <a href="#privacy">隐私政策</a> |
            <a href="#terms">服务条款</a> |
            <a href="#contact">联系我们</a>
        </nav>
    </footer>
</body>
</html>

1.2 多媒体支持增强

HTML5原生支持音频和视频播放,不再依赖Flash等第三方插件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>HTML5多媒体示例</title>
    <style>
        .media-container {
            max-width: 800px;
            margin: 2rem auto;
            padding: 1rem;
        }
        
        .video-player, .audio-player {
            width: 100%;
            margin-bottom: 2rem;
        }
        
        .custom-controls {
            display: flex;
            align-items: center;
            gap: 1rem;
            margin-top: 1rem;
        }
        
        .progress-container {
            flex: 1;
            height: 8px;
            background: #ddd;
            border-radius: 4px;
            overflow: hidden;
        }
        
        .progress-bar {
            height: 100%;
            background: #3498db;
            width: 0%;
            transition: width 0.1s;
        }
        
        .control-btn {
            padding: 0.5rem 1rem;
            border: none;
            background: #3498db;
            color: white;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="media-container">
        <!-- 视频播放器 -->
        <section>
            <h2>视频播放示例</h2>
            <video class="video-player" 
                   controls 
                   preload="metadata"
                   poster="video-poster.jpg"
                   width="800"
                   height="450">
                <!-- 多格式支持,确保浏览器兼容性 -->
                <source src="video.mp4" type="video/mp4">
                <source src="video.webm" type="video/webm">
                <source src="video.ogg" type="video/ogg">
                <!-- 后备内容 -->
                <p>您的浏览器不支持HTML5视频,请<a href="video.mp4">下载视频</a></p>
            </video>
            
            <!-- 自定义视频控制 -->
            <div class="custom-controls" id="videoControls">
                <button class="control-btn" id="playPause">播放/暂停</button>
                <button class="control-btn" id="mute">静音</button>
                <div class="progress-container">
                    <div class="progress-bar" id="videoProgress"></div>
                </div>
                <span id="timeDisplay">00:00 / 00:00</span>
                <input type="range" id="volume" min="0" max="1" step="0.1" value="1">
            </div>
        </section>

        <!-- 音频播放器 -->
        <section>
            <h2>音频播放示例</h2>
            <audio class="audio-player" 
                   controls 
                   preload="metadata"
                   id="audioPlayer">
                <source src="audio.mp3" type="audio/mpeg">
                <source src="audio.ogg" type="audio/ogg">
                <source src="audio.wav" type="audio/wav">
                <p>您的浏览器不支持HTML5音频。</p>
            </audio>
            
            <!-- 音频可视化 -->
            <canvas id="audioVisualizer" width="800" height="100" style="border: 1px solid #ddd; margin-top: 1rem;"></canvas>
        </section>
    </div>

    <script>
        // 自定义视频控制
        const video = document.querySelector('video');
        const playPauseBtn = document.getElementById('playPause');
        const muteBtn = document.getElementById('mute');
        const videoProgress = document.getElementById('videoProgress');
        const timeDisplay = document.getElementById('timeDisplay');
        const volumeControl = document.getElementById('volume');

        playPauseBtn.addEventListener('click', () => {
            if (video.paused) {
                video.play();
            } else {
                video.pause();
            }
        });

        muteBtn.addEventListener('click', () => {
            video.muted = !video.muted;
            muteBtn.textContent = video.muted ? '取消静音' : '静音';
        });

        video.addEventListener('timeupdate', () => {
            const progress = (video.currentTime / video.duration) * 100;
            videoProgress.style.width = `${progress}%`;
            
            const currentTime = formatTime(video.currentTime);
            const duration = formatTime(video.duration);
            timeDisplay.textContent = `${currentTime} / ${duration}`;
        });

        volumeControl.addEventListener('input', () => {
            video.volume = volumeControl.value;
        });

        function formatTime(seconds) {
            const mins = Math.floor(seconds / 60);
            const secs = Math.floor(seconds % 60);
            return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
        }

        // 音频可视化
        const audio = document.getElementById('audioPlayer');
        const canvas = document.getElementById('audioVisualizer');
        const ctx = canvas.getContext('2d');

        // 创建音频上下文和分析器
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const analyser = audioContext.createAnalyser();
        const source = audioContext.createMediaElementSource(audio);

        source.connect(analyser);
        analyser.connect(audioContext.destination);

        analyser.fftSize = 256;
        const bufferLength = analyser.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);

        function drawVisualizer() {
            requestAnimationFrame(drawVisualizer);
            
            analyser.getByteFrequencyData(dataArray);
            
            ctx.fillStyle = 'rgb(255, 255, 255)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            const barWidth = (canvas.width / bufferLength) * 2.5;
            let barHeight;
            let x = 0;
            
            for(let i = 0; i < bufferLength; i++) {
                barHeight = dataArray[i] / 2;
                
                ctx.fillStyle = `rgb(${barHeight + 100}, 50, 50)`;
                ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
                
                x += barWidth + 1;
            }
        }

        audio.addEventListener('play', () => {
            audioContext.resume().then(() => {
                drawVisualizer();
            });
        });
    </script>
</body>
</html>

1.3 Canvas绘图与动画

HTML5 Canvas提供了强大的2D绘图能力,支持复杂的图形和动画效果。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Canvas绘图示例</title>
    <style>
        .canvas-container {
            text-align: center;
            padding: 2rem;
        }
        
        canvas {
            border: 2px solid #34495e;
            border-radius: 8px;
            background: #ecf0f1;
            margin: 1rem;
        }
        
        .controls {
            margin: 1rem 0;
        }
        
        .control-btn {
            padding: 0.5rem 1rem;
            margin: 0 0.5rem;
            border: none;
            background: #3498db;
            color: white;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="canvas-container">
        <h1>HTML5 Canvas 绘图示例</h1>
        
        <div class="controls">
            <button class="control-btn" id="drawBasic">基本图形</button>
            <button class="control-btn" id="drawGradient">渐变效果</button>
            <button class="control-btn" id="drawText">文字效果</button>
            <button class="control-btn" id="startAnimation">开始动画</button>
            <button class="control-btn" id="clearCanvas">清空画布</button>
        </div>
        
        <canvas id="mainCanvas" width="800" height="400">
            您的浏览器不支持Canvas,请升级到现代浏览器。
        </canvas>
    </div>

    <script>
        const canvas = document.getElementById('mainCanvas');
        const ctx = canvas.getContext('2d');
        
        // 基本图形绘制
        function drawBasicShapes() {
            clearCanvas();
            
            // 矩形
            ctx.fillStyle = '#e74c3c';
            ctx.fillRect(50, 50, 100, 80);
            
            ctx.strokeStyle = '#2c3e50';
            ctx.lineWidth = 3;
            ctx.strokeRect(180, 50, 100, 80);
            
            // 圆形
            ctx.beginPath();
            ctx.arc(350, 90, 40, 0, Math.PI * 2);
            ctx.fillStyle = '#3498db';
            ctx.fill();
            ctx.stroke();
            
            // 三角形
            ctx.beginPath();
            ctx.moveTo(450, 50);
            ctx.lineTo(550, 50);
            ctx.lineTo(500, 130);
            ctx.closePath();
            ctx.fillStyle = '#2ecc71';
            ctx.fill();
            ctx.stroke();
            
            // 复杂路径
            ctx.beginPath();
            ctx.moveTo(600, 50);
            ctx.bezierCurveTo(650, 30, 700, 80, 750, 50);
            ctx.bezierCurveTo(780, 70, 750, 130, 700, 110);
            ctx.closePath();
            ctx.fillStyle = '#9b59b6';
            ctx.fill();
            ctx.stroke();
        }
        
        // 渐变效果
        function drawGradients() {
            clearCanvas();
            
            // 线性渐变
            const linearGradient = ctx.createLinearGradient(0, 0, 800, 0);
            linearGradient.addColorStop(0, '#e74c3c');
            linearGradient.addColorStop(0.5, '#f39c12');
            linearGradient.addColorStop(1, '#2ecc71');
            
            ctx.fillStyle = linearGradient;
            ctx.fillRect(50, 50, 700, 100);
            
            // 径向渐变
            const radialGradient = ctx.createRadialGradient(400, 300, 10, 400, 300, 100);
            radialGradient.addColorStop(0, '#3498db');
            radialGradient.addColorStop(1, '#2c3e50');
            
            ctx.fillStyle = radialGradient;
            ctx.beginPath();
            ctx.arc(400, 300, 100, 0, Math.PI * 2);
            ctx.fill();
        }
        
        // 文字效果
        function drawTextEffects() {
            clearCanvas();
            
            ctx.fillStyle = '#2c3e50';
            ctx.font = 'bold 36px Arial';
            ctx.fillText('HTML5 Canvas 文字效果', 50, 80);
            
            // 文字阴影
            ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
            ctx.shadowBlur = 10;
            ctx.shadowOffsetX = 5;
            ctx.shadowOffsetY = 5;
            
            ctx.fillStyle = '#e74c3c';
            ctx.font = 'italic 48px Georgia';
            ctx.fillText('带阴影的文字', 50, 160);
            
            // 重置阴影
            ctx.shadowColor = 'transparent';
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
            
            // 文字渐变
            const textGradient = ctx.createLinearGradient(50, 200, 500, 200);
            textGradient.addColorStop(0, '#e74c3c');
            textGradient.addColorStop(0.5, '#3498db');
            textGradient.addColorStop(1, '#2ecc71');
            
            ctx.fillStyle = textGradient;
            ctx.font = 'bold 42px Arial';
            ctx.fillText('渐变文字效果', 50, 250);
            
            // 文字描边
            ctx.strokeStyle = '#34495e';
            ctx.lineWidth = 2;
            ctx.font = 'bold 36px Arial';
            ctx.strokeText('描边文字', 50, 320);
        }
        
        // 动画
        let animationId;
        function startAnimation() {
            clearCanvas();
            let angle = 0;
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = 100;
            
            function animate() {
                clearCanvas();
                
                // 绘制轨道
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
                ctx.strokeStyle = '#bdc3c7';
                ctx.stroke();
                
                // 计算小球位置
                const x = centerX + Math.cos(angle) * radius;
                const y = centerY + Math.sin(angle) * radius;
                
                // 绘制小球
                ctx.beginPath();
                ctx.arc(x, y, 20, 0, Math.PI * 2);
                const gradient = ctx.createRadialGradient(x-10, y-10, 5, x, y, 20);
                gradient.addColorStop(0, '#e74c3c');
                gradient.addColorStop(1, '#c0392b');
                ctx.fillStyle = gradient;
                ctx.fill();
                
                // 更新角度
                angle += 0.05;
                
                animationId = requestAnimationFrame(animate);
            }
            
            animate();
        }
        
        function clearCanvas() {
            if (animationId) {
                cancelAnimationFrame(animationId);
                animationId = null;
            }
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }
        
        // 事件监听
        document.getElementById('drawBasic').addEventListener('click', drawBasicShapes);
        document.getElementById('drawGradient').addEventListener('click', drawGradients);
        document.getElementById('drawText').addEventListener('click', drawTextEffects);
        document.getElementById('startAnimation').addEventListener('click', startAnimation);
        document.getElementById('clearCanvas').addEventListener('click', clearCanvas);
        
        // 初始绘制
        drawBasicShapes();
    </script>
</body>
</html>

1.4 表单功能增强

HTML5为表单引入了新的输入类型、属性和验证功能。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>HTML5表单增强</title>
    <style>
        .form-container {
            max-width: 600px;
            margin: 2rem auto;
            padding: 2rem;
            background: #f8f9fa;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        .form-group {
            margin-bottom: 1.5rem;
        }
        
        label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: bold;
            color: #2c3e50;
        }
        
        input, select, textarea {
            width: 100%;
            padding: 0.75rem;
            border: 2px solid #bdc3c7;
            border-radius: 4px;
            font-size: 1rem;
            transition: border-color 0.3s;
        }
        
        input:focus, select:focus, textarea:focus {
            outline: none;
            border-color: #3498db;
        }
        
        input:valid {
            border-color: #2ecc71;
        }
        
        input:invalid {
            border-color: #e74c3c;
        }
        
        .error-message {
            color: #e74c3c;
            font-size: 0.875rem;
            margin-top: 0.25rem;
            display: none;
        }
        
        input:invalid + .error-message {
            display: block;
        }
        
        .submit-btn {
            background: #3498db;
            color: white;
            padding: 1rem 2rem;
            border: none;
            border-radius: 4px;
            font-size: 1.1rem;
            cursor: pointer;
            width: 100%;
        }
        
        .submit-btn:hover {
            background: #2980b9;
        }
        
        /* 自定义样式示例 */
        input[type="range"] {
            height: 8px;
            background: #ddd;
            outline: none;
        }
        
        input[type="color"] {
            height: 50px;
            padding: 5px;
        }
        
        .form-section {
            margin-bottom: 2rem;
            padding-bottom: 1rem;
            border-bottom: 1px solid #bdc3c7;
        }
        
        .form-section h3 {
            color: #34495e;
            margin-bottom: 1rem;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <h1>HTML5增强表单示例</h1>
        <form id="enhancedForm" novalidate>
            <!-- 基础输入类型 -->
            <div class="form-section">
                <h3>基础信息</h3>
                
                <div class="form-group">
                    <label for="email">邮箱地址 *</label>
                    <input type="email" id="email" name="email" 
                           placeholder="请输入有效的邮箱地址"
                           required>
                    <div class="error-message">请输入有效的邮箱地址</div>
                </div>
                
                <div class="form-group">
                    <label for="url">个人网站</label>
                    <input type="url" id="url" name="url" 
                           placeholder="https://example.com">
                    <div class="error-message">请输入有效的URL地址</div>
                </div>
                
                <div class="form-group">
                    <label for="phone">手机号码</label>
                    <input type="tel" id="phone" name="phone" 
                           pattern="[0-9]{11}"
                           placeholder="请输入11位手机号码">
                    <div class="error-message">请输入11位手机号码</div>
                </div>
            </div>

            <!-- 新增输入类型 -->
            <div class="form-section">
                <h3>高级输入类型</h3>
                
                <div class="form-group">
                    <label for="birthdate">出生日期</label>
                    <input type="date" id="birthdate" name="birthdate">
                </div>
                
                <div class="form-group">
                    <label for="time">选择时间</label>
                    <input type="time" id="time" name="time">
                </div>
                
                <div class="form-group">
                    <label for="datetime">日期时间</label>
                    <input type="datetime-local" id="datetime" name="datetime">
                </div>
                
                <div class="form-group">
                    <label for="month">选择月份</label>
                    <input type="month" id="month" name="month">
                </div>
                
                <div class="form-group">
                    <label for="week">选择周</label>
                    <input type="week" id="week" name="week">
                </div>
            </div>

            <!-- 范围与颜色选择 -->
            <div class="form-section">
                <h3>范围与颜色</h3>
                
                <div class="form-group">
                    <label for="volume">音量控制: <span id="volumeValue">50</span>%</label>
                    <input type="range" id="volume" name="volume" 
                           min="0" max="100" value="50" step="1">
                </div>
                
                <div class="form-group">
                    <label for="rating">评分: <span id="ratingValue">3</span></label>
                    <input type="range" id="rating" name="rating" 
                           min="1" max="5" value="3" step="1">
                </div>
                
                <div class="form-group">
                    <label for="favcolor">选择喜欢的颜色</label>
                    <input type="color" id="favcolor" name="favcolor" value="#3498db">
                </div>
            </div>

            <!-- 数据列表与搜索 -->
            <div class="form-section">
                <h3>搜索与选择</h3>
                
                <div class="form-group">
                    <label for="browser">选择浏览器</label>
                    <input list="browsers" id="browser" name="browser" 
                           placeholder="输入或选择浏览器">
                    <datalist id="browsers">
                        <option value="Chrome">
                        <option value="Firefox">
                        <option value="Safari">
                        <option value="Edge">
                        <option value="Opera">
                    </datalist>
                </div>
                
                <div class="form-group">
                    <label for="search">搜索</label>
                    <input type="search" id="search" name="search" 
                           placeholder="输入搜索关键词">
                </div>
            </div>

            <!-- 其他增强功能 -->
            <div class="form-section">
                <h3>其他功能</h3>
                
                <div class="form-group">
                    <label for="quantity">数量 (1-10)</label>
                    <input type="number" id="quantity" name="quantity" 
                           min="1" max="10" value="1">
                </div>
                
                <div class="form-group">
                    <label for="progress">进度</label>
                    <progress id="progress" value="75" max="100">75%</progress>
                </div>
                
                <div class="form-group">
                    <label for="file">选择文件</label>
                    <input type="file" id="file" name="file" 
                           accept=".jpg,.png,.pdf"
                           multiple>
                </div>
            </div>

            <button type="submit" class="submit-btn">提交表单</button>
        </form>
    </div>

    <script>
        // 实时更新范围输入的值显示
        const volumeInput = document.getElementById('volume');
        const volumeValue = document.getElementById('volumeValue');
        const ratingInput = document.getElementById('rating');
        const ratingValue = document.getElementById('ratingValue');

        volumeInput.addEventListener('input', () => {
            volumeValue.textContent = volumeInput.value;
        });

        ratingInput.addEventListener('input', () => {
            ratingValue.textContent = ratingInput.value;
        });

        // 表单验证增强
        const form = document.getElementById('enhancedForm');
        
        form.addEventListener('submit', (event) => {
            event.preventDefault();
            
            if (form.checkValidity()) {
                // 表单验证通过
                alert('表单提交成功!');
                // 这里可以添加实际的表单提交逻辑
            } else {
                // 显示自定义验证消息
                const invalidFields = form.querySelectorAll(':invalid');
                invalidFields.forEach(field => {
                    field.reportValidity();
                });
            }
        });

        // 实时验证
        const inputs = form.querySelectorAll('input');
        inputs.forEach(input => {
            input.addEventListener('blur', () => {
                input.checkValidity();
            });
            
            input.addEventListener('input', () => {
                // 输入时移除错误状态
                if (input.validity.valid) {
                    input.style.borderColor = '#2ecc71';
                }
            });
        });

        // 自定义验证示例
        const emailInput = document.getElementById('email');
        emailInput.addEventListener('input', () => {
            if (emailInput.validity.typeMismatch) {
                emailInput.setCustomValidity('请输入有效的邮箱地址,如:user@example.com');
            } else {
                emailInput.setCustomValidity('');
            }
        });
    </script>
</body>
</html>

二、离线存储深度解析

2.1 Web Storage:localStorage和sessionStorage

Web Storage提供了简单易用的键值对存储机制,分为localStorage(持久化存储)和sessionStorage(会话级存储)。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Web Storage 离线存储</title>
    <style>
        .storage-demo {
            max-width: 800px;
            margin: 2rem auto;
            padding: 2rem;
            background: #f8f9fa;
            border-radius: 8px;
        }
        
        .storage-section {
            margin-bottom: 2rem;
            padding: 1.5rem;
            background: white;
            border-radius: 6px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        
        .input-group {
            display: flex;
            gap: 1rem;
            margin-bottom: 1rem;
        }
        
        .input-group input {
            flex: 1;
            padding: 0.75rem;
            border: 2px solid #bdc3c7;
            border-radius: 4px;
        }
        
        .btn {
            padding: 0.75rem 1.5rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
        }
        
        .btn-primary {
            background: #3498db;
            color: white;
        }
        
        .btn-danger {
            background: #e74c3c;
            color: white;
        }
        
        .btn-success {
            background: #2ecc71;
            color: white;
        }
        
        .storage-data {
            background: #ecf0f1;
            padding: 1rem;
            border-radius: 4px;
            margin-top: 1rem;
            font-family: monospace;
            white-space: pre-wrap;
            max-height: 300px;
            overflow-y: auto;
        }
        
        .comparison-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 1rem;
        }
        
        .comparison-table th,
        .comparison-table td {
            border: 1px solid #bdc3c7;
            padding: 0.75rem;
            text-align: left;
        }
        
        .comparison-table th {
            background: #34495e;
            color: white;
        }
        
        .comparison-table tr:nth-child(even) {
            background: #f8f9fa;
        }
    </style>
</head>
<body>
    <div class="storage-demo">
        <h1>Web Storage 离线存储示例</h1>
        
        <!-- localStorage 示例 -->
        <section class="storage-section">
            <h2>localStorage - 持久化存储</h2>
            <p>数据会一直保存,直到明确删除或用户清除浏览器数据</p>
            
            <div class="input-group">
                <input type="text" id="localKey" placeholder="输入键名">
                <input type="text" id="localValue" placeholder="输入值">
                <button class="btn btn-primary" id="saveLocal">保存到localStorage</button>
            </div>
            
            <div>
                <button class="btn btn-success" id="loadLocal">读取localStorage</button>
                <button class="btn btn-danger" id="clearLocal">清空localStorage</button>
                <button class="btn" id="removeLocal">删除指定项</button>
            </div>
            
            <div class="storage-data" id="localData">
                localStorage数据将显示在这里...
            </div>
        </section>

        <!-- sessionStorage 示例 -->
        <section class="storage-section">
            <h2>sessionStorage - 会话存储</h2>
            <p>数据仅在当前浏览器标签页有效,关闭标签页后数据丢失</p>
            
            <div class="input-group">
                <input type="text" id="sessionKey" placeholder="输入键名">
                <input type="text" id="sessionValue" placeholder="输入值">
                <button class="btn btn-primary" id="saveSession">保存到sessionStorage</button>
            </div>
            
            <div>
                <button class="btn btn-success" id="loadSession">读取sessionStorage</button>
                <button class="btn btn-danger" id="clearSession">清空sessionStorage</button>
            </div>
            
            <div class="storage-data" id="sessionData">
                sessionStorage数据将显示在这里...
            </div>
        </section>

        <!-- 特性对比 -->
        <section class="storage-section">
            <h2>localStorage vs sessionStorage 对比</h2>
            
            <table class="comparison-table">
                <thead>
                    <tr>
                        <th>特性</th>
                        <th>localStorage</th>
                        <th>sessionStorage</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>数据生命周期</td>
                        <td>永久存储,直到手动删除</td>
                        <td>会话期间有效,标签页关闭即删除</td>
                    </tr>
                    <tr>
                        <td>作用域</td>
                        <td>同源的所有标签页共享</td>
                        <td>仅当前标签页有效</td>
                    </tr>
                    <tr>
                        <td>存储容量</td>
                        <td>通常5-10MB</td>
                        <td>通常5-10MB</td>
                    </tr>
                    <tr>
                        <td>数据格式</td>
                        <td colspan="2">仅支持字符串,需要序列化对象</td>
                    </tr>
                    <tr>
                        <td>同步/异步</td>
                        <td colspan="2">同步操作,可能阻塞主线程</td>
                    </tr>
                </tbody>
            </table>
        </section>

        <!-- 实际应用示例 -->
        <section class="storage-section">
            <h2>实际应用:用户偏好设置</h2>
            
            <div class="input-group">
                <label>
                    <input type="checkbox" id="darkMode"> 深色模式
                </label>
                <label>
                    <input type="checkbox" id="notifications"> 启用通知
                </label>
                <select id="language">
                    <option value="zh-CN">中文</option>
                    <option value="en-US">English</option>
                    <option value="ja-JP">日本語</option>
                </select>
                <button class="btn btn-primary" id="savePreferences">保存偏好设置</button>
            </div>
            
            <div class="storage-data" id="preferencesData">
                用户偏好设置将显示在这里...
            </div>
        </section>
    </div>

    <script>
        // localStorage 操作
        const saveLocalBtn = document.getElementById('saveLocal');
        const loadLocalBtn = document.getElementById('loadLocal');
        const clearLocalBtn = document.getElementById('clearLocal');
        const removeLocalBtn = document.getElementById('removeLocal');
        const localDataDiv = document.getElementById('localData');

        saveLocalBtn.addEventListener('click', () => {
            const key = document.getElementById('localKey').value;
            const value = document.getElementById('localValue').value;
            
            if (key && value) {
                localStorage.setItem(key, value);
                alert(`已保存: ${key} = ${value}`);
                document.getElementById('localKey').value = '';
                document.getElementById('localValue').value = '';
            } else {
                alert('请输入键名和值');
            }
        });

        loadLocalBtn.addEventListener('click', () => {
            const data = {};
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                data[key] = localStorage.getItem(key);
            }
            localDataDiv.textContent = JSON.stringify(data, null, 2);
        });

        clearLocalBtn.addEventListener('click', () => {
            localStorage.clear();
            localDataDiv.textContent = 'localStorage已清空';
        });

        removeLocalBtn.addEventListener('click', () => {
            const key = document.getElementById('localKey').value;
            if (key) {
                localStorage.removeItem(key);
                alert(`已删除: ${key}`);
            } else {
                alert('请输入要删除的键名');
            }
        });

        // sessionStorage 操作
        const saveSessionBtn = document.getElementById('saveSession');
        const loadSessionBtn = document.getElementById('loadSession');
        const clearSessionBtn = document.getElementById('clearSession');
        const sessionDataDiv = document.getElementById('sessionData');

        saveSessionBtn.addEventListener('click', () => {
            const key = document.getElementById('sessionKey').value;
            const value = document.getElementById('sessionValue').value;
            
            if (key && value) {
                sessionStorage.setItem(key, value);
                alert(`已保存: ${key} = ${value}`);
                document.getElementById('sessionKey').value = '';
                document.getElementById('sessionValue').value = '';
            } else {
                alert('请输入键名和值');
            }
        });

        loadSessionBtn.addEventListener('click', () => {
            const data = {};
            for (let i = 0; i < sessionStorage.length; i++) {
                const key = sessionStorage.key(i);
                data[key] = sessionStorage.getItem(key);
            }
            sessionDataDiv.textContent = JSON.stringify(data, null, 2);
        });

        clearSessionBtn.addEventListener('click', () => {
            sessionStorage.clear();
            sessionDataDiv.textContent = 'sessionStorage已清空';
        });

        // 用户偏好设置
        const savePreferencesBtn = document.getElementById('savePreferences');
        const preferencesDataDiv = document.getElementById('preferencesData');

        // 加载保存的偏好设置
        function loadPreferences() {
            const preferences = JSON.parse(localStorage.getItem('userPreferences') || '{}');
            
            document.getElementById('darkMode').checked = preferences.darkMode || false;
            document.getElementById('notifications').checked = preferences.notifications || false;
            document.getElementById('language').value = preferences.language || 'zh-CN';
            
            preferencesDataDiv.textContent = JSON.stringify(preferences, null, 2);
        }

        savePreferencesBtn.addEventListener('click', () => {
            const preferences = {
                darkMode: document.getElementById('darkMode').checked,
                notifications: document.getElementById('notifications').checked,
                language: document.getElementById('language').value,
                lastUpdated: new Date().toISOString()
            };
            
            localStorage.setItem('userPreferences', JSON.stringify(preferences));
            loadPreferences();
            alert('偏好设置已保存!');
        });

        // 页面加载时读取偏好设置
        document.addEventListener('DOMContentLoaded', loadPreferences);

        // 存储容量检测
        function testStorageCapacity(storage) {
            const testKey = 'storageTest';
            const testValue = 'a'.repeat(1024); // 1KB
            let totalSize = 0;
            
            try {
                while (true) {
                    storage.setItem(testKey + totalSize, testValue.repeat(totalSize + 1));
                    totalSize += 1; // KB
                }
            } catch (e) {
                // 捕获存储空间不足的错误
                for (let i = 0; i < totalSize; i++) {
                    storage.removeItem(testKey + i);
                }
                return totalSize;
            }
        }

        // 显示存储容量信息
        console.log('localStorage 可用容量约:', testStorageCapacity(localStorage), 'KB');
        console.log('sessionStorage 可用容量约:', testStorageCapacity(sessionStorage), 'KB');

        // 存储事件监听(跨标签页通信)
        window.addEventListener('storage', (event) => {
            console.log('存储发生变化:', {
                key: event.key,
                oldValue: event.oldValue,
                newValue: event.newValue,
                url: event.url,
                storageArea: event.storageArea
            });
            
            if (event.key === 'userPreferences') {
                loadPreferences(); // 自动更新偏好设置
            }
        });

        // 高级功能:存储管理类
        class StorageManager {
            constructor(storage = localStorage) {
                this.storage = storage;
            }
            
            setObject(key, value) {
                this.storage.setItem(key, JSON.stringify(value));
            }
            
            getObject(key, defaultValue = null) {
                try {
                    const item = this.storage.getItem(key);
                    return item ? JSON.parse(item) : defaultValue;
                } catch (error) {
                    console.error('解析存储数据失败:', error);
                    return defaultValue;
                }
            }
            
            exists(key) {
                return this.storage.getItem(key) !== null;
            }
            
            getSize() {
                let total = 0;
                for (let key in this.storage) {
                    if (this.storage.hasOwnProperty(key)) {
                        total += (key.length + this.storage.getItem(key).length) * 2; // UTF-16
                    }
                }
                return total;
            }
        }

        // 使用存储管理类
        const storageManager = new StorageManager(localStorage);
        storageManager.setObject('userSettings', {
            theme: 'dark',
            fontSize: 16,
            autoSave: true
        });
        
        const settings = storageManager.getObject('userSettings');
        console.log('用户设置:', settings);
        console.log('存储占用:', storageManager.getSize(), 'bytes');
    </script>
</body>
</html>

2.2 IndexedDB:客户端数据库

IndexedDB是一个功能强大的浏览器内数据库,支持存储大量结构化数据。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>IndexedDB 客户端数据库</title>
    <style>
        .indexeddb-demo {
            max-width: 1000px;
            margin: 2rem auto;
            padding: 2rem;
        }
        
        .db-section {
            background: white;
            padding: 1.5rem;
            margin-bottom: 2rem;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        .form-group {
            margin-bottom: 1rem;
        }
        
        label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: bold;
            color: #2c3e50;
        }
        
        input, select, textarea {
            width: 100%;
            padding: 0.75rem;
            border: 2px solid #bdc3c7;
            border-radius: 4px;
            font-size: 1rem;
        }
        
        .btn {
            padding: 0.75rem 1.5rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-right: 0.5rem;
            margin-bottom: 0.5rem;
        }
        
        .btn-primary { background: #3498db; color: white; }
        .btn-success { background: #2ecc71; color: white; }
        .btn-danger { background: #e74c3c; color: white; }
        .btn-warning { background: #f39c12; color: white; }
        
        .data-display {
            background: #f8f9fa;
            padding: 1rem;
            border-radius: 4px;
            margin-top: 1rem;
            max-height: 400px;
            overflow-y: auto;
        }
        
        .user-card {
            border: 1px solid #bdc3c7;
            border-radius: 6px;
            padding: 1rem;
            margin-bottom: 1rem;
            background: white;
        }
        
        .user-card h4 {
            margin: 0 0 0.5rem 0;
            color: #2c3e50;
        }
        
        .user-card p {
            margin: 0.25rem 0;
            color: #7f8c8d;
        }
        
        .action-buttons {
            margin-top: 1rem;
        }
        
        .status {
            padding: 0.5rem;
            border-radius: 4px;
            margin-bottom: 1rem;
            text-align: center;
        }
        
        .status.success { background: #d5f4e6; color: #27ae60; }
        .status.error { background: #fadbd8; color: #c0392b; }
        .status.info { background: #d6eaf8; color: #2980b9; }
    </style>
</head>
<body>
    <div class="indexeddb-demo">
        <h1>IndexedDB 客户端数据库示例</h1>
        
        <!-- 数据库操作 -->
        <section class="db-section">
            <h2>数据库管理</h2>
            <div id="dbStatus" class="status info">数据库状态: 未连接</div>
            
            <div>
                <button class="btn btn-primary" id="openDB">打开/创建数据库</button>
                <button class="btn btn-danger" id="deleteDB">删除数据库</button>
                <button class="btn btn-warning" id="clearDB">清空所有数据</button>
            </div>
        </section>

        <!-- 用户管理 -->
        <section class="db-section">
            <h2>用户管理</h2>
            
            <div class="form-group">
                <label for="userName">姓名</label>
                <input type="text" id="userName" placeholder="输入用户姓名">
            </div>
            
            <div class="form-group">
                <label for="userEmail">邮箱</label>
                <input type="email" id="userEmail" placeholder="输入用户邮箱">
            </div>
            
            <div class="form-group">
                <label for="userAge">年龄</label>
                <input type="number" id="userAge" placeholder="输入用户年龄" min="0" max="150">
            </div>
            
            <div class="form-group">
                <label for="userDepartment">部门</label>
                <select id="userDepartment">
                    <option value="技术部">技术部</option>
                    <option value="市场部">市场部</option>
                    <option value="销售部">销售部</option>
                    <option value="人事部">人事部</option>
                </select>
            </div>
            
            <div>
                <button class="btn btn-primary" id="addUser">添加用户</button>
                <button class="btn btn-success" id="getAllUsers">获取所有用户</button>
                <button class="btn btn-warning" id="searchUsers">搜索用户</button>
            </div>
            
            <div class="data-display" id="usersDisplay">
                用户数据将显示在这里...
            </div>
        </section>

        <!-- 高级查询 -->
        <section class="db-section">
            <h2>高级查询</h2>
            
            <div class="form-group">
                <label for="searchField">搜索字段</label>
                <select id="searchField">
                    <option value="name">姓名</option>
                    <option value="email">邮箱</option>
                    <option value="department">部门</option>
                </select>
            </div>
            
            <div class="form-group">
                <label for="searchValue">搜索值</label>
                <input type="text" id="searchValue" placeholder="输入搜索内容">
            </div>
            
            <div class="form-group">
                <label for="minAge">最小年龄</label>
                <input type="number" id="minAge" placeholder="最小年龄" min="0">
            </div>
            
            <div class="form-group">
                <label for="maxAge">最大年龄</label>
                <input type="number" id="maxAge" placeholder="最大年龄" min="0">
            </div>
            
            <div>
                <button class="btn btn-primary" id="queryByField">按字段查询</button>
                <button class="btn btn-success" id="queryByAgeRange">按年龄范围查询</button>
                <button class="btn btn-warning" id="queryByIndex">使用索引查询</button>
            </div>
            
            <div class="data-display" id="queryResults">
                查询结果将显示在这里...
            </div>
        </section>

        <!-- 性能测试 -->
        <section class="db-section">
            <h2>性能测试</h2>
            
            <div class="form-group">
                <label for="testCount">测试数据量</label>
                <input type="number" id="testCount" value="1000" min="1" max="10000">
            </div>
            
            <div>
                <button class="btn btn-primary" id="batchInsert">批量插入测试数据</button>
                <button class="btn btn-success" id="performanceTest">性能测试</button>
                <button class="btn btn-danger" id="clearTestData">清空测试数据</button>
            </div>
            
            <div class="data-display" id="performanceResults">
                性能测试结果将显示在这里...
            </div>
        </section>
    </div>

    <script>
        class IndexedDBManager {
            constructor(dbName = 'UserDatabase', version = 1) {
                this.dbName = dbName;
                this.version = version;
                this.db = null;
                this.STORE_NAME = 'users';
            }

            // 打开数据库
            async open() {
                return new Promise((resolve, reject) => {
                    const request = indexedDB.open(this.dbName, this.version);

                    request.onerror = () => reject(request.error);
                    request.onsuccess = () => {
                        this.db = request.result;
                        resolve(this.db);
                    };

                    request.onupgradeneeded = (event) => {
                        const db = event.target.result;
                        
                        // 删除已存在的对象存储(如果存在)
                        if (db.objectStoreNames.contains(this.STORE_NAME)) {
                            db.deleteObjectStore(this.STORE_NAME);
                        }
                        
                        // 创建新的对象存储
                        const store = db.createObjectStore(this.STORE_NAME, {
                            keyPath: 'id',
                            autoIncrement: true
                        });
                        
                        // 创建索引
                        store.createIndex('name', 'name', { unique: false });
                        store.createIndex('email', 'email', { unique: true });
                        store.createIndex('age', 'age', { unique: false });
                        store.createIndex('department', 'department', { unique: false });
                        store.createIndex('createdAt', 'createdAt', { unique: false });
                    };
                });
            }

            // 关闭数据库
            close() {
                if (this.db) {
                    this.db.close();
                    this.db = null;
                }
            }

            // 删除数据库
            async deleteDatabase() {
                this.close();
                return new Promise((resolve, reject) => {
                    const request = indexedDB.deleteDatabase(this.dbName);
                    request.onerror = () => reject(request.error);
                    request.onsuccess = () => resolve();
                });
            }

            // 添加用户
            async addUser(user) {
                return this.withTransaction('readwrite', (store) => {
                    user.createdAt = new Date().toISOString();
                    return store.add(user);
                });
            }

            // 获取所有用户
            async getAllUsers() {
                return this.withTransaction('readonly', (store) => {
                    return store.getAll();
                });
            }

            // 根据ID获取用户
            async getUserById(id) {
                return this.withTransaction('readonly', (store) => {
                    return store.get(id);
                });
            }

            // 更新用户
            async updateUser(id, updates) {
                return this.withTransaction('readwrite', async (store) => {
                    const user = await store.get(id);
                    if (user) {
                        const updatedUser = { ...user, ...updates, updatedAt: new Date().toISOString() };
                        return store.put(updatedUser);
                    }
                    throw new Error('用户不存在');
                });
            }

            // 删除用户
            async deleteUser(id) {
                return this.withTransaction('readwrite', (store) => {
                    return store.delete(id);
                });
            }

            // 按字段查询
            async queryByField(field, value) {
                return this.withTransaction('readonly', (store) => {
                    const index = store.index(field);
                    return index.getAll(value);
                });
            }

            // 按年龄范围查询
            async queryByAgeRange(minAge, maxAge) {
                return this.withTransaction('readonly', (store) => {
                    const index = store.index('age');
                    const range = IDBKeyRange.bound(minAge, maxAge);
                    return index.getAll(range);
                });
            }

            // 使用索引进行复杂查询
            async queryByIndex(indexName, value) {
                return this.withTransaction('readonly', (store) => {
                    const index = store.index(indexName);
                    return index.getAll(value);
                });
            }

            // 批量插入
            async batchInsert(users) {
                return this.withTransaction('readwrite', async (store) => {
                    const promises = users.map(user => {
                        user.createdAt = new Date().toISOString();
                        return store.add(user);
                    });
                    return Promise.all(promises);
                });
            }

            // 清空所有数据
            async clearAll() {
                return this.withTransaction('readwrite', (store) => {
                    return store.clear();
                });
            }

            // 事务包装器
            async withTransaction(mode, operation) {
                if (!this.db) {
                    throw new Error('数据库未连接');
                }

                return new Promise((resolve, reject) => {
                    const transaction = this.db.transaction([this.STORE_NAME], mode);
                    const store = transaction.objectStore(this.STORE_NAME);
                    
                    transaction.oncomplete = () => resolve();
                    transaction.onerror = () => reject(transaction.error);
                    
                    const result = operation(store);
                    if (result instanceof IDBRequest) {
                        result.onsuccess = () => resolve(result.result);
                        result.onerror = () => reject(result.error);
                    } else if (result instanceof Promise) {
                        result.then(resolve).catch(reject);
                    } else {
                        resolve(result);
                    }
                });
            }
        }

        // 创建数据库管理器实例
        const dbManager = new IndexedDBManager();

        // DOM 元素
        const dbStatus = document.getElementById('dbStatus');
        const openDBBtn = document.getElementById('openDB');
        const deleteDBBtn = document.getElementById('deleteDB');
        const clearDBBtn = document.getElementById('clearDB');
        const addUserBtn = document.getElementById('addUser');
        const getAllUsersBtn = document.getElementById('getAllUsers');
        const searchUsersBtn = document.getElementById('searchUsers');
        const usersDisplay = document.getElementById('usersDisplay');
        const queryResults = document.getElementById('queryResults');
        const performanceResults = document.getElementById('performanceResults');

        // 更新状态显示
        function updateStatus(message, type = 'info') {
            dbStatus.textContent = `数据库状态: ${message}`;
            dbStatus.className = `status ${type}`;
        }

        // 打开数据库
        openDBBtn.addEventListener('click', async () => {
            try {
                updateStatus('正在打开数据库...', 'info');
                await dbManager.open();
                updateStatus('数据库连接成功', 'success');
            } catch (error) {
                updateStatus(`连接失败: ${error.message}`, 'error');
            }
        });

        // 删除数据库
        deleteDBBtn.addEventListener('click', async () => {
            if (confirm('确定要删除整个数据库吗?此操作不可逆!')) {
                try {
                    updateStatus('正在删除数据库...', 'info');
                    await dbManager.deleteDatabase();
                    updateStatus('数据库已删除', 'success');
                    usersDisplay.textContent = '数据库已删除';
                } catch (error) {
                    updateStatus(`删除失败: ${error.message}`, 'error');
                }
            }
        });

        // 清空数据
        clearDBBtn.addEventListener('click', async () => {
            if (confirm('确定要清空所有数据吗?')) {
                try {
                    await dbManager.clearAll();
                    updateStatus('所有数据已清空', 'success');
                    usersDisplay.textContent = '数据已清空';
                } catch (error) {
                    updateStatus(`清空失败: ${error.message}`, 'error');
                }
            }
        });

        // 添加用户
        addUserBtn.addEventListener('click', async () => {
            const user = {
                name: document.getElementById('userName').value,
                email: document.getElementById('userEmail').value,
                age: parseInt(document.getElementById('userAge').value),
                department: document.getElementById('userDepartment').value
            };

            if (!user.name || !user.email) {
                alert('请填写姓名和邮箱');
                return;
            }

            try {
                await dbManager.addUser(user);
                updateStatus('用户添加成功', 'success');
                document.getElementById('userName').value = '';
                document.getElementById('userEmail').value = '';
                document.getElementById('userAge').value = '';
            } catch (error) {
                updateStatus(`添加失败: ${error.message}`, 'error');
            }
        });

        // 获取所有用户
        getAllUsersBtn.addEventListener('click', async () => {
            try {
                const users = await dbManager.getAllUsers();
                displayUsers(users, usersDisplay);
                updateStatus(`获取到 ${users.length} 个用户`, 'success');
            } catch (error) {
                updateStatus(`获取失败: ${error.message}`, 'error');
            }
        });

        // 显示用户数据
        function displayUsers(users, container) {
            if (users.length === 0) {
                container.innerHTML = '<p>没有找到用户数据</p>';
                return;
            }

            container.innerHTML = users.map(user => `
                <div class="user-card">
                    <h4>${user.name} (ID: ${user.id})</h4>
                    <p><strong>邮箱:</strong> ${user.email}</p>
                    <p><strong>年龄:</strong> ${user.age}</p>
                    <p><strong>部门:</strong> ${user.department}</p>
                    <p><strong>创建时间:</strong> ${new Date(user.createdAt).toLocaleString()}</p>
                    ${user.updatedAt ? `<p><strong>更新时间:</strong> ${new Date(user.updatedAt).toLocaleString()}</p>` : ''}
                    <div class="action-buttons">
                        <button class="btn btn-warning" onclick="updateUser(${user.id})">更新</button>
                        <button class="btn btn-danger" onclick="deleteUser(${user.id})">删除</button>
                    </div>
                </div>
            `).join('');
        }

        // 删除用户
        window.deleteUser = async (id) => {
            if (confirm('确定要删除这个用户吗?')) {
                try {
                    await dbManager.deleteUser(id);
                    updateStatus('用户删除成功', 'success');
                    // 刷新显示
                    const users = await dbManager.getAllUsers();
                    displayUsers(users, usersDisplay);
                } catch (error) {
                    updateStatus(`删除失败: ${error.message}`, 'error');
                }
            }
        };

        // 更新用户
        window.updateUser = async (id) => {
            const newName = prompt('请输入新的姓名:');
            if (newName) {
                try {
                    await dbManager.updateUser(id, { name: newName });
                    updateStatus('用户更新成功', 'success');
                    // 刷新显示
                    const users = await dbManager.getAllUsers();
                    displayUsers(users, usersDisplay);
                } catch (error) {
                    updateStatus(`更新失败: ${error.message}`, 'error');
                }
            }
        };

        // 查询功能
        document.getElementById('queryByField').addEventListener('click', async () => {
            const field = document.getElementById('searchField').value;
            const value = document.getElementById('searchValue').value;
            
            if (!value) {
                alert('请输入搜索值');
                return;
            }

            try {
                const results = await dbManager.queryByField(field, value);
                displayUsers(results, queryResults);
                updateStatus(`找到 ${results.length} 个匹配的用户`, 'success');
            } catch (error) {
                updateStatus(`查询失败: ${error.message}`, 'error');
            }
        });

        document.getElementById('queryByAgeRange').addEventListener('click', async () => {
            const minAge = parseInt(document.getElementById('minAge').value) || 0;
            const maxAge = parseInt(document.getElementById('maxAge').value) || 150;
            
            try {
                const results = await dbManager.queryByAgeRange(minAge, maxAge);
                displayUsers(results, queryResults);
                updateStatus(`在年龄范围 ${minAge}-${maxAge} 内找到 ${results.length} 个用户`, 'success');
            } catch (error) {
                updateStatus(`查询失败: ${error.message}`, 'error');
            }
        });

        // 性能测试
        document.getElementById('batchInsert').addEventListener('click', async () => {
            const count = parseInt(document.getElementById('testCount').value) || 1000;
            
            // 生成测试数据
            const testUsers = Array.from({ length: count }, (_, i) => ({
                name: `测试用户${i + 1}`,
                email: `user${i + 1}@test.com`,
                age: Math.floor(Math.random() * 50) + 18,
                department: ['技术部', '市场部', '销售部', '人事部'][Math.floor(Math.random() * 4)]
            }));

            try {
                const startTime = performance.now();
                await dbManager.batchInsert(testUsers);
                const endTime = performance.now();
                
                const duration = (endTime - startTime).toFixed(2);
                performanceResults.innerHTML = `
                    <p><strong>批量插入性能测试</strong></p>
                    <p>数据量: ${count} 条记录</p>
                    <p>耗时: ${duration} 毫秒</p>
                    <p>平均每条: ${(duration / count).toFixed(3)} 毫秒</p>
                `;
                updateStatus(`批量插入 ${count} 条记录完成`, 'success');
            } catch (error) {
                updateStatus(`批量插入失败: ${error.message}`, 'error');
            }
        });

        // 初始化
        document.addEventListener('DOMContentLoaded', async () => {
            try {
                await dbManager.open();
                updateStatus('数据库连接成功', 'success');
            } catch (error) {
                updateStatus(`自动连接失败: ${error.message}`, 'error');
            }
        });
    </script>
</body>
</html>

三、Service Worker与离线应用

Service Worker是现代PWA(渐进式Web应用)的核心技术,可以实现真正的离线体验。

// service-worker.js
const CACHE_NAME = 'my-pwa-cache-v1.2.0';
const API_CACHE_NAME = 'api-cache-v1.0.0';

// 需要缓存的资源
const STATIC_RESOURCES = [
    '/',
    '/index.html',
    '/styles/main.css',
    '/scripts/app.js',
    '/images/logo.png',
    '/images/offline.png',
    '/manifest.json'
];

// 需要缓存的API端点
const API_ENDPOINTS = [
    '/api/user/profile',
    '/api/products/list'
];

// 安装事件
self.addEventListener('install', (event) => {
    console.log('Service Worker 安装中...');
    
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then((cache) => {
                console.log('缓存静态资源');
                return cache.addAll(STATIC_RESOURCES);
            })
            .then(() => {
                console.log('跳过等待,立即激活');
                return self.skipWaiting();
            })
            .catch((error) => {
                console.error('缓存失败:', error);
            })
    );
});

// 激活事件
self.addEventListener('activate', (event) => {
    console.log('Service Worker 激活中...');
    
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames.map((cacheName) => {
                    // 删除旧缓存
                    if (cacheName !== CACHE_NAME && cacheName !== API_CACHE_NAME) {
                        console.log('删除旧缓存:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        }).then(() => {
            console.log('Service Worker 激活完成,接管所有客户端');
            return self.clients.claim();
        })
    );
});

// 获取事件 - 核心缓存策略
self.addEventListener('fetch', (event) => {
    const { request } = event;
    const url = new URL(request.url);

    // 跳过非GET请求和浏览器扩展
    if (request.method !== 'GET' || url.protocol === 'chrome-extension:') {
        return;
    }

    // 处理不同资源的缓存策略
    if (isStaticResource(request)) {
        // 静态资源:缓存优先
        event.respondWith(staticResourceStrategy(request));
    } else if (isAPIRequest(request)) {
        // API请求:网络优先,失败时使用缓存
        event.respondWith(apiResourceStrategy(request));
    } else if (isImageRequest(request)) {
        // 图片:缓存优先,支持离线降级
        event.respondWith(imageResourceStrategy(request));
    } else {
        // 其他资源:网络优先
        event.respondWith(networkFirstStrategy(request));
    }
});

// 判断是否为静态资源
function isStaticResource(request) {
    const url = new URL(request.url);
    return STATIC_RESOURCES.some(resource => 
        url.pathname.endsWith(resource)
    );
}

// 判断是否为API请求
function isAPIRequest(request) {
    const url = new URL(request.url);
    return url.pathname.startsWith('/api/');
}

// 判断是否为图片请求
function isImageRequest(request) {
    return request.destination === 'image';
}

// 静态资源策略:缓存优先
async function staticResourceStrategy(request) {
    const cache = await caches.open(CACHE_NAME);
    
    try {
        // 首先尝试从缓存获取
        const cachedResponse = await cache.match(request);
        if (cachedResponse) {
            // 同时更新缓存(在后台)
            updateCache(request, cache);
            return cachedResponse;
        }
        
        // 缓存中没有,从网络获取
        const networkResponse = await fetch(request);
        
        // 缓存新资源
        if (networkResponse.status === 200) {
            await cache.put(request, networkResponse.clone());
        }
        
        return networkResponse;
    } catch (error) {
        // 网络失败,返回离线页面
        console.log('网络请求失败,返回离线页面');
        return getOfflineResponse(request);
    }
}

// API资源策略:网络优先
async function apiResourceStrategy(request) {
    const cache = await caches.open(API_CACHE_NAME);
    
    try {
        // 首先尝试网络请求
        const networkResponse = await fetch(request);
        
        // 缓存成功的GET请求
        if (networkResponse.status === 200 && request.method === 'GET') {
            await cache.put(request, networkResponse.clone());
        }
        
        return networkResponse;
    } catch (error) {
        // 网络失败,尝试从缓存获取
        console.log('API请求失败,尝试使用缓存');
        const cachedResponse = await cache.match(request);
        
        if (cachedResponse) {
            return cachedResponse;
        }
        
        // 缓存也没有,返回错误响应
        return new Response(
            JSON.stringify({ error: '网络不可用,且无缓存数据' }),
            { 
                status: 503,
                headers: { 'Content-Type': 'application/json' }
            }
        );
    }
}

// 图片资源策略:缓存优先,支持降级
async function imageResourceStrategy(request) {
    const cache = await caches.open(CACHE_NAME);
    
    try {
        // 首先尝试缓存
        const cachedResponse = await cache.match(request);
        if (cachedResponse) {
            return cachedResponse;
        }
        
        // 缓存中没有,从网络获取
        const networkResponse = await fetch(request);
        
        // 缓存图片
        if (networkResponse.status === 200) {
            await cache.put(request, networkResponse.clone());
        }
        
        return networkResponse;
    } catch (error) {
        // 网络失败,返回离线图片
        console.log('图片加载失败,返回离线图片');
        return cache.match('/images/offline.png');
    }
}

// 网络优先策略
async function networkFirstStrategy(request) {
    try {
        const networkResponse = await fetch(request);
        return networkResponse;
    } catch (error) {
        const cache = await caches.open(CACHE_NAME);
        const cachedResponse = await cache.match(request);
        
        if (cachedResponse) {
            return cachedResponse;
        }
        
        return getOfflineResponse(request);
    }
}

// 后台更新缓存
async function updateCache(request, cache) {
    try {
        const networkResponse = await fetch(request);
        if (networkResponse.status === 200) {
            await cache.put(request, networkResponse);
        }
    } catch (error) {
        // 后台更新失败,静默处理
        console.log('后台缓存更新失败:', error);
    }
}

// 获取离线响应
async function getOfflineResponse(request) {
    const url = new URL(request.url);
    
    // 如果是HTML请求,返回离线页面
    if (request.destination === 'document' || 
        request.headers.get('Accept').includes('text/html')) {
        const cache = await caches.open(CACHE_NAME);
        const offlinePage = await cache.match('/offline.html');
        return offlinePage || new Response('离线模式:请检查网络连接');
    }
    
    // 其他类型的请求返回适当的错误响应
    return new Response('资源暂不可用,请检查网络连接', {
        status: 503,
        statusText: 'Service Unavailable'
    });
}

// 后台同步
self.addEventListener('sync', (event) => {
    console.log('后台同步事件:', event.tag);
    
    if (event.tag === 'background-sync') {
        event.waitUntil(doBackgroundSync());
    }
});

// 执行后台同步
async function doBackgroundSync() {
    try {
        // 获取待同步的数据
        const pendingActions = await getPendingActions();
        
        for (const action of pendingActions) {
            await syncAction(action);
        }
        
        console.log('后台同步完成');
    } catch (error) {
        console.error('后台同步失败:', error);
    }
}

// 推送通知
self.addEventListener('push', (event) => {
    console.log('接收到推送消息:', event);
    
    if (!event.data) return;
    
    const data = event.data.json();
    const options = {
        body: data.body,
        icon: '/images/icon-192.png',
        badge: '/images/badge-72.png',
        vibrate: [100, 50, 100],
        data: {
            url: data.url
        },
        actions: [
            {
                action: 'open',
                title: '打开应用'
            },
            {
                action: 'close',
                title: '关闭'
            }
        ]
    };
    
    event.waitUntil(
        self.registration.showNotification(data.title, options)
    );
});

// 通知点击事件
self.addEventListener('notificationclick', (event) => {
    console.log('通知被点击:', event);
    
    event.notification.close();
    
    if (event.action === 'open') {
        // 打开特定URL
        event.waitUntil(
            clients.openWindow(event.notification.data.url)
        );
    } else {
        // 默认打开应用主页
        event.waitUntil(
            clients.openWindow('/')
        );
    }
});

// 从IndexedDB获取待同步的操作
async function getPendingActions() {
    // 这里需要实现从IndexedDB读取待同步数据的逻辑
    return [];
}

// 同步单个操作
async function syncAction(action) {
    try {
        const response = await fetch(action.url, {
            method: action.method,
            headers: action.headers,
            body: action.body
        });
        
        if (response.ok) {
            // 同步成功,从待同步列表中移除
            await removePendingAction(action.id);
        }
    } catch (error) {
        console.error('同步操作失败:', error);
        throw error;
    }
}

// 从IndexedDB移除已同步的操作
async function removePendingAction(actionId) {
    // 实现从IndexedDB删除数据的逻辑
}

Service Worker注册和使用

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PWA离线应用示例</title>
    <meta name="theme-color" content="#3498db">
    <meta name="description" content="一个支持离线使用的渐进式Web应用">
    
    <!-- PWA Manifest -->
    <link rel="manifest" href="/manifest.json">
    
    <!-- iOS Safari -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="default">
    <meta name="apple-mobile-web-app-title" content="离线应用">
    <link rel="apple-touch-icon" href="/images/icon-192.png">
    
    <style>
        .app-container {
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
            font-family: Arial, sans-serif;
        }
        
        .status-indicator {
            padding: 0.5rem;
            border-radius: 4px;
            margin-bottom: 1rem;
            text-align: center;
            font-weight: bold;
        }
        
        .online { background: #d5f4e6; color: #27ae60; }
        .offline { background: #fadbd8; color: #c0392b; }
        
        .feature-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 1rem;
            margin: 2rem 0;
        }
        
        .feature-card {
            border: 1px solid #bdc3c7;
            border-radius: 8px;
            padding: 1.5rem;
            text-align: center;
        }
        
        .btn {
            padding: 0.75rem 1.5rem;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
            margin: 0.5rem;
        }
        
        .btn-primary { background: #3498db; color: white; }
        .btn-success { background: #2ecc71; color: white; }
        
        .cache-status {
            background: #f8f9fa;
            padding: 1rem;
            border-radius: 4px;
            margin: 1rem 0;
        }
    </style>
</head>
<body>
    <div class="app-container">
        <h1>📱 PWA离线应用示例</h1>
        
        <div id="connectionStatus" class="status-indicator">
            检测网络状态...
        </div>
        
        <section>
            <h2>应用功能</h2>
            <div class="feature-grid">
                <div class="feature-card">
                    <h3>🔗 网络状态</h3>
                    <p>实时检测在线/离线状态</p>
                    <button class="btn btn-primary" id="checkNetwork">检查网络</button>
                </div>
                
                <div class="feature-card">
                    <h3>💾 缓存管理</h3>
                    <p>管理Service Worker缓存</p>
                    <button class="btn btn-primary" id="clearCache">清空缓存</button>
                </div>
                
                <div class="feature-card">
                    <h3>🔄 后台同步</h3>
                    <p>在网络恢复时同步数据</p>
                    <button class="btn btn-primary" id="backgroundSync">触发同步</button>
                </div>
                
                <div class="feature-card">
                    <h3>🔔 推送通知</h3>
                    <p>接收推送消息通知</p>
                    <button class="btn btn-primary" id="requestNotification">请求通知权限</button>
                </div>
            </div>
        </section>
        
        <section>
            <h2>缓存状态</h2>
            <div class="cache-status" id="cacheStatus">
                正在检查缓存状态...
            </div>
            <button class="btn btn-success" id="updateCache">更新缓存</button>
        </section>
        
        <section>
            <h2>离线功能测试</h2>
            <p>尝试断开网络连接,然后刷新页面测试离线功能。</p>
            <div id="offlineTest">
                <button class="btn btn-primary" id="testOffline">测试离线API</button>
                <div id="offlineResult"></div>
            </div>
        </section>
    </div>

    <script>
        class PWAApp {
            constructor() {
                this.registration = null;
                this.init();
            }
            
            async init() {
                await this.registerServiceWorker();
                this.setupNetworkDetection();
                this.setupEventListeners();
                this.updateCacheStatus();
            }
            
            // 注册Service Worker
            async registerServiceWorker() {
                if ('serviceWorker' in navigator) {
                    try {
                        this.registration = await navigator.serviceWorker.register('/service-worker.js');
                        console.log('Service Worker 注册成功:', this.registration);
                        
                        // 监听Service Worker更新
                        this.registration.addEventListener('updatefound', () => {
                            const newWorker = this.registration.installing;
                            console.log('发现Service Worker更新:', newWorker);
                            
                            newWorker.addEventListener('statechange', () => {
                                console.log('Service Worker状态变化:', newWorker.state);
                            });
                        });
                        
                    } catch (error) {
                        console.error('Service Worker 注册失败:', error);
                    }
                } else {
                    console.log('当前浏览器不支持 Service Worker');
                }
            }
            
            // 设置网络状态检测
            setupNetworkDetection() {
                const statusElement = document.getElementById('connectionStatus');
                
                function updateNetworkStatus() {
                    if (navigator.onLine) {
                        statusElement.textContent = '✅ 在线状态';
                        statusElement.className = 'status-indicator online';
                    } else {
                        statusElement.textContent = '❌ 离线状态';
                        statusElement.className = 'status-indicator offline';
                    }
                }
                
                // 初始状态
                updateNetworkStatus();
                
                // 监听网络状态变化
                window.addEventListener('online', updateNetworkStatus);
                window.addEventListener('offline', updateNetworkStatus);
            }
            
            // 设置事件监听
            setupEventListeners() {
                // 检查网络状态
                document.getElementById('checkNetwork').addEventListener('click', () => {
                    alert(`当前网络状态: ${navigator.onLine ? '在线' : '离线'}`);
                });
                
                // 清空缓存
                document.getElementById('clearCache').addEventListener('click', async () => {
                    if (confirm('确定要清空所有缓存吗?')) {
                        await this.clearAllCaches();
                        this.updateCacheStatus();
                    }
                });
                
                // 后台同步
                document.getElementById('backgroundSync').addEventListener('click', async () => {
                    await this.triggerBackgroundSync();
                });
                
                // 请求通知权限
                document.getElementById('requestNotification').addEventListener('click', () => {
                    this.requestNotificationPermission();
                });
                
                // 更新缓存
                document.getElementById('updateCache').addEventListener('click', async () => {
                    await this.updateServiceWorker();
                });
                
                // 测试离线功能
                document.getElementById('testOffline').addEventListener('click', async () => {
                    await this.testOfflineFunctionality();
                });
            }
            
            // 清空所有缓存
            async clearAllCaches() {
                try {
                    const cacheNames = await caches.keys();
                    await Promise.all(
                        cacheNames.map(cacheName => caches.delete(cacheName))
                    );
                    alert('所有缓存已清空');
                } catch (error) {
                    console.error('清空缓存失败:', error);
                    alert('清空缓存失败');
                }
            }
            
            // 触发后台同步
            async triggerBackgroundSync() {
                if ('sync' in this.registration) {
                    try {
                        await this.registration.sync.register('background-sync');
                        alert('后台同步已注册,将在网络可用时执行');
                    } catch (error) {
                        console.error('后台同步注册失败:', error);
                        alert('后台同步注册失败');
                    }
                } else {
                    alert('当前浏览器不支持后台同步');
                }
            }
            
            // 请求通知权限
            async requestNotificationPermission() {
                if ('Notification' in window) {
                    const permission = await Notification.requestPermission();
                    
                    if (permission === 'granted') {
                        alert('通知权限已授予');
                        
                        // 显示测试通知
                        if ('serviceWorker' in navigator) {
                            this.registration.showNotification('测试通知', {
                                body: '这是一条测试推送消息',
                                icon: '/images/icon-192.png',
                                vibrate: [200, 100, 200]
                            });
                        }
                    } else {
                        alert('通知权限被拒绝');
                    }
                } else {
                    alert('当前浏览器不支持通知功能');
                }
            }
            
            // 更新Service Worker
            async updateServiceWorker() {
                if (this.registration) {
                    try {
                        await this.registration.update();
                        alert('Service Worker 已更新');
                        this.updateCacheStatus();
                    } catch (error) {
                        console.error('Service Worker 更新失败:', error);
                        alert('更新失败');
                    }
                }
            }
            
            // 更新缓存状态显示
            async updateCacheStatus() {
                const statusElement = document.getElementById('cacheStatus');
                
                try {
                    const cacheNames = await caches.keys();
                    let statusHTML = '<h3>当前缓存:</h3>';
                    
                    for (const cacheName of cacheNames) {
                        const cache = await caches.open(cacheName);
                        const requests = await cache.keys();
                        
                        statusHTML += `
                            <div>
                                <strong>${cacheName}:</strong> ${requests.length} 个资源
                                <ul>
                                    ${requests.slice(0, 5).map(req => 
                                        `<li>${new URL(req.url).pathname}</li>`
                                    ).join('')}
                                    ${requests.length > 5 ? `<li>... 还有 ${requests.length - 5} 个资源</li>` : ''}
                                </ul>
                            </div>
                        `;
                    }
                    
                    statusElement.innerHTML = statusHTML;
                } catch (error) {
                    statusElement.innerHTML = '无法获取缓存状态';
                }
            }
            
            // 测试离线功能
            async testOfflineFunctionality() {
                const resultElement = document.getElementById('offlineResult');
                resultElement.innerHTML = '<p>测试中...</p>';
                
                try {
                    // 尝试访问一个API端点
                    const response = await fetch('/api/test');
                    const data = await response.json();
                    resultElement.innerHTML = `<p style="color: green;">✅ 在线API调用成功: ${JSON.stringify(data)}</p>`;
                } catch (error) {
                    resultElement.innerHTML = `<p style="color: orange;">⚠️ 在线API调用失败,可能处于离线状态</p>`;
                    
                    // 尝试从缓存获取
                    try {
                        const cache = await caches.open('api-cache-v1.0.0');
                        const cachedResponse = await cache.match('/api/test');
                        
                        if (cachedResponse) {
                            const cachedData = await cachedResponse.json();
                            resultElement.innerHTML += `<p style="color: blue;">💾 从缓存获取数据: ${JSON.stringify(cachedData)}</p>`;
                        } else {
                            resultElement.innerHTML += `<p style="color: red;">❌ 缓存中也没有数据</p>`;
                        }
                    } catch (cacheError) {
                        resultElement.innerHTML += `<p style="color: red;">❌ 缓存访问失败</p>`;
                    }
                }
            }
        }
        
        // 初始化应用
        document.addEventListener('DOMContentLoaded', () => {
            new PWAApp();
        });
        
        // 监听Service Worker消息
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.addEventListener('message', (event) => {
                console.log('接收到Service Worker消息:', event.data);
                
                // 处理不同类型的消息
                switch (event.data.type) {
                    case 'CACHE_UPDATED':
                        console.log('缓存已更新:', event.data.payload);
                        break;
                    case 'SYNC_COMPLETED':
                        console.log('后台同步完成:', event.data.payload);
                        break;
                }
            });
        }
    </script>
</body>
</html>

总结

HTML5新特性核心要点:

  1. 语义化标签:提高可访问性和SEO
  2. 多媒体支持:原生音视频播放
  3. Canvas绘图:强大的2D图形和动画
  4. 增强表单:新的输入类型和验证
  5. 本地存储:Web Storage和IndexedDB
  6. 离线应用:Service Worker和PWA

离线存储技术对比:

技术 存储容量 数据类型 查询能力 适用场景
localStorage 5-10MB 键值对 简单 小量数据、用户偏好
sessionStorage 5-10MB 键值对 简单 会话数据、临时状态
IndexedDB 大量 结构化 强大 复杂数据、离线应用
Service Worker 动态 网络响应 缓存策略 离线资源、PWA

最佳实践建议:

  1. 合理选择存储方案:根据数据量和复杂度选择合适的技术
  2. 错误处理:所有存储操作都要有完善的错误处理
  3. 容量管理:定期清理过期数据,避免存储空间不足
  4. 数据安全:敏感数据要加密存储
  5. 性能优化:批量操作、异步处理提升用户体验
  6. 渐进增强:确保应用在离线状态下仍能基本使用

掌握这些HTML5新特性和离线存储技术,可以构建出功能丰富、用户体验优秀的现代Web应用。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐