Skip to content

9.5 HTML5其他重要API

关键字

History API, File API, WebSocket, Notification API, Device Orientation API, pushState, FileReader, Blob, ArrayBuffer, notification

学习目标

  • 理解HTML5 History API的工作原理和应用场景
  • 掌握File API处理文件的各种方法
  • 学会使用WebSocket实现实时通信
  • 了解Notification API的通知机制
  • 掌握Device Orientation API获取设备方向信息
  • 能够构建综合的API应用

9.5.1 History API

History API基础

HTML5 History API允许JavaScript操作浏览器的历史记录,实现无刷新页面导航。

基本History API HTML结构

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>History API基础</title>
</head>
<body>
    <h1>History API示例</h1>
    
    <!-- 导航菜单 -->
    <nav class="navigation">
        <h2>导航菜单</h2>
        <ul role="menubar">
            <li role="none">
                <button class="nav-btn" data-page="home" role="menuitem">首页</button>
            </li>
            <li role="none">
                <button class="nav-btn" data-page="about" role="menuitem">关于我们</button>
            </li>
            <li role="none">
                <button class="nav-btn" data-page="products" role="menuitem">产品展示</button>
            </li>
            <li role="none">
                <button class="nav-btn" data-page="contact" role="menuitem">联系我们</button>
            </li>
        </ul>
    </nav>
    
    <!-- 页面内容区域 -->
    <main class="content-area">
        <div id="page-content" role="main" aria-live="polite">
            <!-- 页面内容将动态加载 -->
        </div>
    </main>
    
    <!-- 历史记录控制 -->
    <section class="history-controls">
        <h2>历史记录控制</h2>
        <button id="go-back">后退</button>
        <button id="go-forward">前进</button>
        <button id="go-specific">跳转到指定页面</button>
        <button id="replace-state">替换当前状态</button>
    </section>
    
    <!-- 状态信息 -->
    <section class="state-info">
        <h2>状态信息</h2>
        <div id="current-state" role="status">
            <p>当前URL: <span id="current-url">-</span></p>
            <p>页面状态: <span id="page-state">-</span></p>
            <p>历史长度: <span id="history-length">-</span></p>
        </div>
    </section>
    
    <script>
        // History API基础实现
        document.addEventListener('DOMContentLoaded', function() {
            const navButtons = document.querySelectorAll('.nav-btn');
            const pageContent = document.getElementById('page-content');
            const currentUrl = document.getElementById('current-url');
            const pageState = document.getElementById('page-state');
            const historyLength = document.getElementById('history-length');
            
            // 页面数据
            const pages = {
                home: {
                    title: '首页',
                    content: '<h2>欢迎来到首页</h2><p>这是网站的主页内容。</p>'
                },
                about: {
                    title: '关于我们',
                    content: '<h2>关于我们</h2><p>这里是关于我们的详细信息。</p>'
                },
                products: {
                    title: '产品展示',
                    content: '<h2>产品展示</h2><p>查看我们的产品系列。</p>'
                },
                contact: {
                    title: '联系我们',
                    content: '<h2>联系我们</h2><p>通过以下方式联系我们。</p>'
                }
            };
            
            // 初始化页面
            initializePage();
            
            // 导航按钮事件
            navButtons.forEach(button => {
                button.addEventListener('click', function() {
                    const pageId = this.dataset.page;
                    navigateToPage(pageId);
                });
            });
            
            // 历史记录控制
            document.getElementById('go-back').addEventListener('click', function() {
                window.history.back();
            });
            
            document.getElementById('go-forward').addEventListener('click', function() {
                window.history.forward();
            });
            
            document.getElementById('go-specific').addEventListener('click', function() {
                window.history.go(-2); // 后退2步
            });
            
            document.getElementById('replace-state').addEventListener('click', function() {
                const currentPage = getCurrentPage();
                const newState = {
                    page: currentPage,
                    timestamp: Date.now(),
                    replaced: true
                };
                
                window.history.replaceState(
                    newState,
                    pages[currentPage].title + ' (已替换)',
                    '?page=' + currentPage + '&replaced=true'
                );
                
                updateStateInfo();
            });
            
            // 监听popstate事件
            window.addEventListener('popstate', function(event) {
                if (event.state) {
                    const pageId = event.state.page;
                    loadPage(pageId, false);
                } else {
                    // 处理没有状态的情况
                    const urlParams = new URLSearchParams(window.location.search);
                    const pageId = urlParams.get('page') || 'home';
                    loadPage(pageId, false);
                }
                updateStateInfo();
            });
            
            // 初始化页面
            function initializePage() {
                const urlParams = new URLSearchParams(window.location.search);
                const initialPage = urlParams.get('page') || 'home';
                
                // 设置初始状态
                const initialState = {
                    page: initialPage,
                    timestamp: Date.now()
                };
                
                window.history.replaceState(
                    initialState,
                    pages[initialPage].title,
                    '?page=' + initialPage
                );
                
                loadPage(initialPage, false);
                updateStateInfo();
            }
            
            // 导航到指定页面
            function navigateToPage(pageId) {
                if (!pages[pageId]) return;
                
                const state = {
                    page: pageId,
                    timestamp: Date.now()
                };
                
                window.history.pushState(
                    state,
                    pages[pageId].title,
                    '?page=' + pageId
                );
                
                loadPage(pageId, true);
                updateStateInfo();
            }
            
            // 加载页面内容
            function loadPage(pageId, animate = false) {
                if (!pages[pageId]) return;
                
                const page = pages[pageId];
                
                if (animate) {
                    pageContent.style.opacity = '0';
                    setTimeout(() => {
                        pageContent.innerHTML = page.content;
                        document.title = page.title;
                        pageContent.style.opacity = '1';
                        
                        // 更新导航状态
                        updateNavigation(pageId);
                    }, 150);
                } else {
                    pageContent.innerHTML = page.content;
                    document.title = page.title;
                    updateNavigation(pageId);
                }
            }
            
            // 更新导航状态
            function updateNavigation(activePageId) {
                navButtons.forEach(button => {
                    if (button.dataset.page === activePageId) {
                        button.classList.add('active');
                        button.setAttribute('aria-current', 'page');
                    } else {
                        button.classList.remove('active');
                        button.removeAttribute('aria-current');
                    }
                });
            }
            
            // 获取当前页面ID
            function getCurrentPage() {
                const urlParams = new URLSearchParams(window.location.search);
                return urlParams.get('page') || 'home';
            }
            
            // 更新状态信息
            function updateStateInfo() {
                currentUrl.textContent = window.location.href;
                pageState.textContent = getCurrentPage();
                historyLength.textContent = window.history.length;
            }
        });
    </script>
</body>
</html>

单页应用路由器

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SPA路由器</title>
</head>
<body>
    <h1>单页应用路由器</h1>
    
    <!-- 路由导航 -->
    <nav class="spa-navigation">
        <h2>路由导航</h2>
        <a href="/home" class="route-link">首页</a>
        <a href="/blog" class="route-link">博客</a>
        <a href="/blog/article/123" class="route-link">文章详情</a>
        <a href="/user/profile" class="route-link">用户资料</a>
        <a href="/settings" class="route-link">设置</a>
    </nav>
    
    <!-- 路由视图 -->
    <main class="route-view">
        <div id="app" role="main" aria-live="polite"></div>
    </main>
    
    <!-- 路由信息 -->
    <section class="route-info">
        <h2>路由信息</h2>
        <div id="route-details">
            <p>当前路由: <span id="current-route">-</span></p>
            <p>路由参数: <span id="route-params">-</span></p>
            <p>查询参数: <span id="query-params">-</span></p>
        </div>
    </section>
    
    <script>
        // 单页应用路由器实现
        class SPARouter {
            constructor() {
                this.routes = new Map();
                this.currentRoute = null;
                this.init();
            }
            
            // 初始化路由器
            init() {
                this.setupRoutes();
                this.bindEvents();
                this.handleRoute();
            }
            
            // 设置路由
            setupRoutes() {
                // 定义路由规则
                this.addRoute('/', this.renderHome);
                this.addRoute('/home', this.renderHome);
                this.addRoute('/blog', this.renderBlog);
                this.addRoute('/blog/article/:id', this.renderArticle);
                this.addRoute('/user/profile', this.renderProfile);
                this.addRoute('/settings', this.renderSettings);
                this.addRoute('*', this.renderNotFound); // 404页面
            }
            
            // 添加路由
            addRoute(path, handler) {
                this.routes.set(path, handler);
            }
            
            // 绑定事件
            bindEvents() {
                // 阻止默认链接行为
                document.addEventListener('click', (e) => {
                    if (e.target.matches('.route-link')) {
                        e.preventDefault();
                        const path = e.target.getAttribute('href');
                        this.navigate(path);
                    }
                });
                
                // 监听浏览器后退/前进
                window.addEventListener('popstate', (e) => {
                    this.handleRoute();
                });
            }
            
            // 导航到指定路由
            navigate(path, replace = false) {
                const state = { path, timestamp: Date.now() };
                
                if (replace) {
                    window.history.replaceState(state, '', path);
                } else {
                    window.history.pushState(state, '', path);
                }
                
                this.handleRoute();
            }
            
            // 处理路由
            handleRoute() {
                const path = window.location.pathname;
                const route = this.matchRoute(path);
                
                if (route) {
                    this.currentRoute = route;
                    route.handler.call(this, route.params);
                    this.updateRouteInfo(path, route.params);
                } else {
                    this.renderNotFound();
                    this.updateRouteInfo(path, {});
                }
            }
            
            // 匹配路由
            matchRoute(path) {
                for (let [routePath, handler] of this.routes) {
                    if (routePath === '*') continue; // 跳过通配符
                    
                    const params = this.extractParams(routePath, path);
                    if (params !== null) {
                        return { path: routePath, handler, params };
                    }
                }
                
                // 如果没有匹配到,返回404路由
                return { path: '*', handler: this.routes.get('*'), params: {} };
            }
            
            // 提取路由参数
            extractParams(routePath, actualPath) {
                const routeSegments = routePath.split('/');
                const pathSegments = actualPath.split('/');
                
                if (routeSegments.length !== pathSegments.length) {
                    return null;
                }
                
                const params = {};
                
                for (let i = 0; i < routeSegments.length; i++) {
                    const routeSegment = routeSegments[i];
                    const pathSegment = pathSegments[i];
                    
                    if (routeSegment.startsWith(':')) {
                        // 动态参数
                        const paramName = routeSegment.substring(1);
                        params[paramName] = pathSegment;
                    } else if (routeSegment !== pathSegment) {
                        // 静态段不匹配
                        return null;
                    }
                }
                
                return params;
            }
            
            // 渲染首页
            renderHome() {
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>首页</h2>
                        <p>欢迎来到单页应用首页!</p>
                        <p>这是使用History API实现的路由系统。</p>
                    </div>
                `;
            }
            
            // 渲染博客页面
            renderBlog() {
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>博客</h2>
                        <p>这是博客列表页面。</p>
                        <ul>
                            <li><a href="/blog/article/1" class="route-link">第一篇文章</a></li>
                            <li><a href="/blog/article/2" class="route-link">第二篇文章</a></li>
                            <li><a href="/blog/article/3" class="route-link">第三篇文章</a></li>
                        </ul>
                    </div>
                `;
            }
            
            // 渲染文章页面
            renderArticle(params) {
                const articleId = params.id;
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>文章详情</h2>
                        <p>正在显示文章ID: ${articleId}</p>
                        <p>这是文章的详细内容...</p>
                        <a href="/blog" class="route-link">返回博客列表</a>
                    </div>
                `;
            }
            
            // 渲染用户资料页面
            renderProfile() {
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>用户资料</h2>
                        <p>这是用户资料页面。</p>
                        <form>
                            <label>姓名: <input type="text" value="张三"></label>
                            <label>邮箱: <input type="email" value="zhangsan@example.com"></label>
                            <button type="submit">保存</button>
                        </form>
                    </div>
                `;
            }
            
            // 渲染设置页面
            renderSettings() {
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>设置</h2>
                        <p>这是系统设置页面。</p>
                        <div class="settings-options">
                            <label><input type="checkbox" checked> 启用通知</label>
                            <label><input type="checkbox"> 自动保存</label>
                            <label><input type="checkbox" checked> 深色模式</label>
                        </div>
                    </div>
                `;
            }
            
            // 渲染404页面
            renderNotFound() {
                document.getElementById('app').innerHTML = `
                    <div class="page">
                        <h2>页面未找到</h2>
                        <p>抱歉,您访问的页面不存在。</p>
                        <a href="/home" class="route-link">返回首页</a>
                    </div>
                `;
            }
            
            // 更新路由信息
            updateRouteInfo(path, params) {
                document.getElementById('current-route').textContent = path;
                document.getElementById('route-params').textContent = JSON.stringify(params);
                
                const urlParams = new URLSearchParams(window.location.search);
                const queryParamsObj = {};
                for (let [key, value] of urlParams) {
                    queryParamsObj[key] = value;
                }
                document.getElementById('query-params').textContent = JSON.stringify(queryParamsObj);
            }
        }
        
        // 启动路由器
        document.addEventListener('DOMContentLoaded', function() {
            new SPARouter();
        });
    </script>
</body>
</html>

9.5.2 File API

File API基础

HTML5 File API提供了在Web应用中处理文件的能力,包括读取文件内容、获取文件信息等。

文件读取和处理

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File API文件处理</title>
</head>
<body>
    <h1>File API文件处理示例</h1>
    
    <!-- 文件选择区域 -->
    <section class="file-selection">
        <h2>文件选择</h2>
        <div class="file-inputs">
            <label for="single-file">选择单个文件:</label>
            <input type="file" id="single-file" accept="*/*">
            
            <label for="multiple-files">选择多个文件:</label>
            <input type="file" id="multiple-files" multiple accept="image/*,text/*">
            
            <label for="image-only">仅图像文件:</label>
            <input type="file" id="image-only" accept="image/*">
        </div>
    </section>
    
    <!-- 拖放区域 -->
    <section class="drag-drop-area">
        <h2>拖放上传</h2>
        <div id="drop-zone" class="drop-zone">
            <p>将文件拖拽到此区域</p>
            <p>或点击选择文件</p>
        </div>
    </section>
    
    <!-- 文件信息显示 -->
    <section class="file-info">
        <h2>文件信息</h2>
        <div id="file-list" role="list"></div>
    </section>
    
    <!-- 文件内容预览 -->
    <section class="file-preview">
        <h2>文件预览</h2>
        <div id="preview-area"></div>
    </section>
    
    <!-- 文件处理选项 -->
    <section class="file-processing">
        <h2>文件处理</h2>
        <div class="processing-options">
            <button id="read-as-text">读取为文本</button>
            <button id="read-as-data-url">读取为数据URL</button>
            <button id="read-as-array-buffer">读取为ArrayBuffer</button>
            <button id="create-blob">创建Blob</button>
            <button id="slice-file">分片处理</button>
        </div>
        <div id="processing-result" role="region" aria-live="polite"></div>
    </section>
    
    <script>
        // File API文件处理实现
        document.addEventListener('DOMContentLoaded', function() {
            const singleFileInput = document.getElementById('single-file');
            const multipleFilesInput = document.getElementById('multiple-files');
            const imageOnlyInput = document.getElementById('image-only');
            const dropZone = document.getElementById('drop-zone');
            const fileList = document.getElementById('file-list');
            const previewArea = document.getElementById('preview-area');
            const processingResult = document.getElementById('processing-result');
            
            let selectedFiles = [];
            
            // 文件选择事件
            singleFileInput.addEventListener('change', handleFileSelect);
            multipleFilesInput.addEventListener('change', handleFileSelect);
            imageOnlyInput.addEventListener('change', handleFileSelect);
            
            // 拖放事件
            dropZone.addEventListener('dragover', handleDragOver);
            dropZone.addEventListener('drop', handleDrop);
            dropZone.addEventListener('click', () => multipleFilesInput.click());
            
            // 文件处理按钮
            document.getElementById('read-as-text').addEventListener('click', () => {
                if (selectedFiles.length > 0) {
                    readFileAsText(selectedFiles[0]);
                }
            });
            
            document.getElementById('read-as-data-url').addEventListener('click', () => {
                if (selectedFiles.length > 0) {
                    readFileAsDataURL(selectedFiles[0]);
                }
            });
            
            document.getElementById('read-as-array-buffer').addEventListener('click', () => {
                if (selectedFiles.length > 0) {
                    readFileAsArrayBuffer(selectedFiles[0]);
                }
            });
            
            document.getElementById('create-blob').addEventListener('click', createBlobExample);
            document.getElementById('slice-file').addEventListener('click', sliceFileExample);
            
            // 处理文件选择
            function handleFileSelect(event) {
                const files = event.target.files;
                selectedFiles = Array.from(files);
                displayFileInfo(selectedFiles);
                previewFiles(selectedFiles);
            }
            
            // 处理拖拽
            function handleDragOver(event) {
                event.preventDefault();
                event.dataTransfer.dropEffect = 'copy';
                dropZone.classList.add('dragover');
            }
            
            // 处理文件拖放
            function handleDrop(event) {
                event.preventDefault();
                dropZone.classList.remove('dragover');
                
                const files = event.dataTransfer.files;
                selectedFiles = Array.from(files);
                displayFileInfo(selectedFiles);
                previewFiles(selectedFiles);
            }
            
            // 显示文件信息
            function displayFileInfo(files) {
                let listHtml = '';
                
                files.forEach((file, index) => {
                    listHtml += `
                        <div class="file-item" role="listitem">
                            <h3>文件 ${index + 1}</h3>
                            <p><strong>名称:</strong> ${file.name}</p>
                            <p><strong>大小:</strong> ${formatFileSize(file.size)}</p>
                            <p><strong>类型:</strong> ${file.type || '未知'}</p>
                            <p><strong>最后修改:</strong> ${new Date(file.lastModified).toLocaleString()}</p>
                        </div>
                    `;
                });
                
                fileList.innerHTML = listHtml;
            }
            
            // 预览文件
            function previewFiles(files) {
                previewArea.innerHTML = '';
                
                files.forEach(file => {
                    if (file.type.startsWith('image/')) {
                        previewImage(file);
                    } else if (file.type.startsWith('text/')) {
                        previewText(file);
                    } else {
                        previewGeneric(file);
                    }
                });
            }
            
            // 预览图片
            function previewImage(file) {
                const reader = new FileReader();
                
                reader.onload = function(e) {
                    const img = document.createElement('img');
                    img.src = e.target.result;
                    img.style.maxWidth = '200px';
                    img.style.maxHeight = '200px';
                    img.alt = file.name;
                    
                    const container = document.createElement('div');
                    container.className = 'preview-item';
                    container.appendChild(img);
                    container.appendChild(document.createElement('br'));
                    container.appendChild(document.createTextNode(file.name));
                    
                    previewArea.appendChild(container);
                };
                
                reader.readAsDataURL(file);
            }
            
            // 预览文本
            function previewText(file) {
                const reader = new FileReader();
                
                reader.onload = function(e) {
                    const content = e.target.result;
                    const preview = content.substring(0, 200) + (content.length > 200 ? '...' : '');
                    
                    const container = document.createElement('div');
                    container.className = 'preview-item';
                    container.innerHTML = `
                        <h4>${file.name}</h4>
                        <pre>${preview}</pre>
                    `;
                    
                    previewArea.appendChild(container);
                };
                
                reader.readAsText(file);
            }
            
            // 预览其他文件
            function previewGeneric(file) {
                const container = document.createElement('div');
                container.className = 'preview-item';
                container.innerHTML = `
                    <h4>${file.name}</h4>
                    <p>文件类型: ${file.type || '未知'}</p>
                    <p>无法预览此文件类型</p>
                `;
                
                previewArea.appendChild(container);
            }
            
            // 读取文件为文本
            function readFileAsText(file) {
                const reader = new FileReader();
                
                reader.onload = function(e) {
                    processingResult.innerHTML = `
                        <h3>文本内容</h3>
                        <p>文件: ${file.name}</p>
                        <p>字符数: ${e.target.result.length}</p>
                        <textarea readonly>${e.target.result}</textarea>
                    `;
                };
                
                reader.onerror = function() {
                    processingResult.innerHTML = `<p style="color: red;">读取文件失败</p>`;
                };
                
                reader.readAsText(file, 'UTF-8');
            }
            
            // 读取文件为数据URL
            function readFileAsDataURL(file) {
                const reader = new FileReader();
                
                reader.onload = function(e) {
                    const dataURL = e.target.result;
                    processingResult.innerHTML = `
                        <h3>数据URL</h3>
                        <p>文件: ${file.name}</p>
                        <p>数据长度: ${dataURL.length}</p>
                        <p>前100个字符:</p>
                        <textarea readonly>${dataURL.substring(0, 100)}...</textarea>
                    `;
                };
                
                reader.readAsDataURL(file);
            }
            
            // 读取文件为ArrayBuffer
            function readFileAsArrayBuffer(file) {
                const reader = new FileReader();
                
                reader.onload = function(e) {
                    const buffer = e.target.result;
                    const view = new Uint8Array(buffer);
                    const first10Bytes = Array.from(view.slice(0, 10));
                    
                    processingResult.innerHTML = `
                        <h3>ArrayBuffer</h3>
                        <p>文件: ${file.name}</p>
                        <p>缓冲区大小: ${buffer.byteLength} bytes</p>
                        <p>前10个字节: [${first10Bytes.join(', ')}]</p>
                    `;
                };
                
                reader.readAsArrayBuffer(file);
            }
            
            // 创建Blob示例
            function createBlobExample() {
                const data = ['这是一个Blob对象的示例内容。\n', '可以包含多个字符串。'];
                const blob = new Blob(data, { type: 'text/plain' });
                
                const url = URL.createObjectURL(blob);
                
                processingResult.innerHTML = `
                    <h3>Blob对象</h3>
                    <p>大小: ${blob.size} bytes</p>
                    <p>类型: ${blob.type}</p>
                    <p><a href="${url}" download="example.txt">下载Blob文件</a></p>
                `;
                
                // 清理URL
                setTimeout(() => URL.revokeObjectURL(url), 10000);
            }
            
            // 文件分片示例
            function sliceFileExample() {
                if (selectedFiles.length === 0) {
                    processingResult.innerHTML = '<p>请先选择一个文件</p>';
                    return;
                }
                
                const file = selectedFiles[0];
                const chunkSize = 1024; // 1KB
                const chunks = Math.ceil(file.size / chunkSize);
                
                let resultHtml = `
                    <h3>文件分片</h3>
                    <p>文件: ${file.name}</p>
                    <p>总大小: ${formatFileSize(file.size)}</p>
                    <p>分片大小: ${chunkSize} bytes</p>
                    <p>分片数量: ${chunks}</p>
                    <div class="chunks">
                `;
                
                for (let i = 0; i < Math.min(chunks, 5); i++) {
                    const start = i * chunkSize;
                    const end = Math.min(start + chunkSize, file.size);
                    const chunk = file.slice(start, end);
                    
                    resultHtml += `
                        <div class="chunk">
                            <h4>分片 ${i + 1}</h4>
                            <p>位置: ${start} - ${end}</p>
                            <p>大小: ${chunk.size} bytes</p>
                        </div>
                    `;
                }
                
                if (chunks > 5) {
                    resultHtml += `<p>... 还有 ${chunks - 5} 个分片</p>`;
                }
                
                resultHtml += '</div>';
                
                processingResult.innerHTML = resultHtml;
            }
            
            // 格式化文件大小
            function formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }
        });
    </script>
</body>
</html>

9.5.3 WebSocket基础

WebSocket连接和通信

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket基础</title>
</head>
<body>
    <h1>WebSocket实时通信示例</h1>
    
    <!-- 连接控制 -->
    <section class="connection-section">
        <h2>连接控制</h2>
        <div class="connection-form">
            <label for="server-url">服务器地址:</label>
            <input type="url" id="server-url" value="wss://echo.websocket.org" placeholder="WebSocket服务器地址">
            
            <div class="connection-buttons">
                <button id="connect-btn">连接</button>
                <button id="disconnect-btn" disabled>断开连接</button>
            </div>
        </div>
        
        <div class="connection-status">
            <p>连接状态: <span id="status" role="status">未连接</span></p>
            <p>协议: <span id="protocol">-</span></p>
            <p>扩展: <span id="extensions">-</span></p>
        </div>
    </section>
    
    <!-- 消息发送 -->
    <section class="message-section">
        <h2>消息发送</h2>
        <div class="message-form">
            <label for="message-input">消息内容:</label>
            <textarea id="message-input" placeholder="输入要发送的消息" rows="3"></textarea>
            
            <div class="message-options">
                <label>
                    <input type="radio" name="message-type" value="text" checked>
                    文本消息
                </label>
                <label>
                    <input type="radio" name="message-type" value="json">
                    JSON消息
                </label>
                <label>
                    <input type="radio" name="message-type" value="binary">
                    二进制消息
                </label>
            </div>
            
            <button id="send-btn" disabled>发送消息</button>
        </div>
    </section>
    
    <!-- 消息记录 -->
    <section class="messages-section">
        <h2>消息记录</h2>
        <div class="message-controls">
            <button id="clear-messages">清空消息</button>
            <button id="export-messages">导出消息</button>
            <label>
                <input type="checkbox" id="auto-scroll" checked>
                自动滚动
            </label>
        </div>
        <div id="messages" class="messages-container" role="log" aria-live="polite"></div>
    </section>
    
    <!-- 统计信息 -->
    <section class="stats-section">
        <h2>统计信息</h2>
        <div class="stats-grid">
            <div class="stat-item">
                <h3>发送消息</h3>
                <span id="sent-count">0</span>
            </div>
            <div class="stat-item">
                <h3>接收消息</h3>
                <span id="received-count">0</span>
            </div>
            <div class="stat-item">
                <h3>连接时长</h3>
                <span id="connection-time">00:00:00</span>
            </div>
            <div class="stat-item">
                <h3>数据传输</h3>
                <span id="data-transfer">0 KB</span>
            </div>
        </div>
    </section>
    
    <script>
        // WebSocket基础实现
        document.addEventListener('DOMContentLoaded', function() {
            const serverUrlInput = document.getElementById('server-url');
            const connectBtn = document.getElementById('connect-btn');
            const disconnectBtn = document.getElementById('disconnect-btn');
            const messageInput = document.getElementById('message-input');
            const sendBtn = document.getElementById('send-btn');
            const messagesContainer = document.getElementById('messages');
            const statusSpan = document.getElementById('status');
            const protocolSpan = document.getElementById('protocol');
            const extensionsSpan = document.getElementById('extensions');
            const autoScrollCheckbox = document.getElementById('auto-scroll');
            
            let websocket = null;
            let connectionStartTime = null;
            let connectionTimer = null;
            let messageStats = {
                sent: 0,
                received: 0,
                totalBytes: 0
            };
            
            // 连接按钮事件
            connectBtn.addEventListener('click', connectWebSocket);
            disconnectBtn.addEventListener('click', disconnectWebSocket);
            
            // 消息发送
            sendBtn.addEventListener('click', sendMessage);
            messageInput.addEventListener('keydown', function(e) {
                if (e.key === 'Enter' && e.ctrlKey) {
                    sendMessage();
                }
            });
            
            // 其他控制
            document.getElementById('clear-messages').addEventListener('click', clearMessages);
            document.getElementById('export-messages').addEventListener('click', exportMessages);
            
            // 连接WebSocket
            function connectWebSocket() {
                const url = serverUrlInput.value.trim();
                if (!url) {
                    alert('请输入WebSocket服务器地址');
                    return;
                }
                
                try {
                    websocket = new WebSocket(url);
                    
                    websocket.onopen = handleOpen;
                    websocket.onmessage = handleMessage;
                    websocket.onclose = handleClose;
                    websocket.onerror = handleError;
                    
                    updateUI('connecting');
                    addMessage('系统', '正在连接...', 'system');
                    
                } catch (error) {
                    addMessage('错误', '连接失败: ' + error.message, 'error');
                }
            }
            
            // 断开连接
            function disconnectWebSocket() {
                if (websocket) {
                    websocket.close(1000, '用户主动断开');
                }
            }
            
            // 处理连接打开
            function handleOpen(event) {
                connectionStartTime = Date.now();
                startConnectionTimer();
                
                updateUI('connected');
                updateConnectionInfo();
                addMessage('系统', '连接已建立', 'system');
            }
            
            // 处理接收消息
            function handleMessage(event) {
                const data = event.data;
                let messageContent = '';
                let messageType = '';
                
                if (typeof data === 'string') {
                    messageContent = data;
                    messageType = 'text';
                } else if (data instanceof Blob) {
                    messageContent = `二进制数据 (${data.size} bytes)`;
                    messageType = 'binary';
                } else if (data instanceof ArrayBuffer) {
                    messageContent = `ArrayBuffer (${data.byteLength} bytes)`;
                    messageType = 'binary';
                }
                
                messageStats.received++;
                messageStats.totalBytes += data.length || data.size || data.byteLength || 0;
                
                addMessage('服务器', messageContent, 'received');
                updateStats();
            }
            
            // 处理连接关闭
            function handleClose(event) {
                stopConnectionTimer();
                updateUI('disconnected');
                
                let reason = '';
                if (event.code === 1000) {
                    reason = '正常关闭';
                } else if (event.code === 1001) {
                    reason = '端点离开';
                } else if (event.code === 1006) {
                    reason = '连接异常断开';
                } else {
                    reason = `关闭代码: ${event.code}`;
                }
                
                addMessage('系统', `连接已关闭 (${reason})`, 'system');
            }
            
            // 处理错误
            function handleError(event) {
                addMessage('错误', 'WebSocket错误', 'error');
            }
            
            // 发送消息
            function sendMessage() {
                if (!websocket || websocket.readyState !== WebSocket.OPEN) {
                    alert('WebSocket未连接');
                    return;
                }
                
                const message = messageInput.value.trim();
                if (!message) {
                    alert('请输入消息内容');
                    return;
                }
                
                const messageType = document.querySelector('input[name="message-type"]:checked').value;
                
                try {
                    let dataToSend;
                    
                    switch (messageType) {
                        case 'text':
                            dataToSend = message;
                            break;
                        case 'json':
                            try {
                                JSON.parse(message); // 验证JSON格式
                                dataToSend = message;
                            } catch (e) {
                                alert('无效的JSON格式');
                                return;
                            }
                            break;
                        case 'binary':
                            const encoder = new TextEncoder();
                            dataToSend = encoder.encode(message);
                            break;
                    }
                    
                    websocket.send(dataToSend);
                    
                    messageStats.sent++;
                    messageStats.totalBytes += message.length;
                    
                    addMessage('客户端', message, 'sent');
                    messageInput.value = '';
                    updateStats();
                    
                } catch (error) {
                    addMessage('错误', '发送失败: ' + error.message, 'error');
                }
            }
            
            // 添加消息到显示区域
            function addMessage(sender, content, type) {
                const messageElement = document.createElement('div');
                messageElement.className = `message ${type}`;
                
                const timestamp = new Date().toLocaleTimeString();
                messageElement.innerHTML = `
                    <div class="message-header">
                        <span class="sender">${sender}</span>
                        <span class="timestamp">${timestamp}</span>
                    </div>
                    <div class="message-content">${escapeHtml(content)}</div>
                `;
                
                messagesContainer.appendChild(messageElement);
                
                // 自动滚动
                if (autoScrollCheckbox.checked) {
                    messagesContainer.scrollTop = messagesContainer.scrollHeight;
                }
                
                // 限制消息数量
                if (messagesContainer.children.length > 100) {
                    messagesContainer.removeChild(messagesContainer.firstChild);
                }
            }
            
            // 更新UI状态
            function updateUI(state) {
                switch (state) {
                    case 'connecting':
                        connectBtn.disabled = true;
                        disconnectBtn.disabled = true;
                        sendBtn.disabled = true;
                        statusSpan.textContent = '连接中...';
                        break;
                    case 'connected':
                        connectBtn.disabled = true;
                        disconnectBtn.disabled = false;
                        sendBtn.disabled = false;
                        statusSpan.textContent = '已连接';
                        break;
                    case 'disconnected':
                        connectBtn.disabled = false;
                        disconnectBtn.disabled = true;
                        sendBtn.disabled = true;
                        statusSpan.textContent = '未连接';
                        break;
                }
            }
            
            // 更新连接信息
            function updateConnectionInfo() {
                if (websocket) {
                    protocolSpan.textContent = websocket.protocol || '无';
                    extensionsSpan.textContent = websocket.extensions || '无';
                }
            }
            
            // 启动连接计时器
            function startConnectionTimer() {
                connectionTimer = setInterval(() => {
                    if (connectionStartTime) {
                        const elapsed = Date.now() - connectionStartTime;
                        const hours = Math.floor(elapsed / 3600000);
                        const minutes = Math.floor((elapsed % 3600000) / 60000);
                        const seconds = Math.floor((elapsed % 60000) / 1000);
                        
                        document.getElementById('connection-time').textContent = 
                            `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
                    }
                }, 1000);
            }
            
            // 停止连接计时器
            function stopConnectionTimer() {
                if (connectionTimer) {
                    clearInterval(connectionTimer);
                    connectionTimer = null;
                }
            }
            
            // 更新统计信息
            function updateStats() {
                document.getElementById('sent-count').textContent = messageStats.sent;
                document.getElementById('received-count').textContent = messageStats.received;
                document.getElementById('data-transfer').textContent = 
                    (messageStats.totalBytes / 1024).toFixed(2) + ' KB';
            }
            
            // 清空消息
            function clearMessages() {
                messagesContainer.innerHTML = '';
            }
            
            // 导出消息
            function exportMessages() {
                const messages = Array.from(messagesContainer.children).map(msg => {
                    const sender = msg.querySelector('.sender').textContent;
                    const timestamp = msg.querySelector('.timestamp').textContent;
                    const content = msg.querySelector('.message-content').textContent;
                    return `[${timestamp}] ${sender}: ${content}`;
                }).join('\n');
                
                const blob = new Blob([messages], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                
                const a = document.createElement('a');
                a.href = url;
                a.download = `websocket-messages-${Date.now()}.txt`;
                a.click();
                
                URL.revokeObjectURL(url);
            }
            
            // HTML转义
            function escapeHtml(text) {
                const div = document.createElement('div');
                div.textContent = text;
                return div.innerHTML;
            }
        });
    </script>
</body>
</html>

9.5.4 Notification API

桌面通知基础

HTML5 Notification API允许网页向用户显示桌面通知,即使浏览器窗口不在前台也能工作。

通知权限和基本使用

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Notification API示例</title>
</head>
<body>
    <h1>桌面通知API示例</h1>
    
    <!-- 权限管理 -->
    <section class="permission-section">
        <h2>通知权限</h2>
        <div class="permission-status">
            <p>当前权限状态: <span id="permission-status" role="status">检查中...</span></p>
            <button id="request-permission">请求通知权限</button>
        </div>
    </section>
    
    <!-- 基本通知 -->
    <section class="basic-notifications">
        <h2>基本通知</h2>
        <div class="notification-form">
            <label for="title-input">通知标题:</label>
            <input type="text" id="title-input" value="通知标题" placeholder="输入通知标题">
            
            <label for="body-input">通知内容:</label>
            <textarea id="body-input" placeholder="输入通知内容">这是一条测试通知</textarea>
            
            <button id="send-basic">发送基本通知</button>
        </div>
    </section>
    
    <!-- 高级通知 -->
    <section class="advanced-notifications">
        <h2>高级通知</h2>
        <div class="advanced-form">
            <div class="form-group">
                <label for="icon-input">图标URL:</label>
                <input type="url" id="icon-input" placeholder="通知图标URL">
            </div>
            
            <div class="form-group">
                <label for="image-input">图片URL:</label>
                <input type="url" id="image-input" placeholder="通知图片URL">
            </div>
            
            <div class="form-group">
                <label for="tag-input">标签:</label>
                <input type="text" id="tag-input" placeholder="通知标签">
            </div>
            
            <div class="form-group">
                <label for="badge-input">徽章URL:</label>
                <input type="url" id="badge-input" placeholder="徽章图标URL">
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" id="require-interaction">
                    需要用户交互
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" id="silent">
                    静音通知
                </label>
            </div>
            
            <button id="send-advanced">发送高级通知</button>
        </div>
    </section>
    
    <!-- 通知模板 -->
    <section class="notification-templates">
        <h2>通知模板</h2>
        <div class="template-buttons">
            <button id="welcome-notification">欢迎通知</button>
            <button id="reminder-notification">提醒通知</button>
            <button id="update-notification">更新通知</button>
            <button id="error-notification">错误通知</button>
        </div>
    </section>
    
    <!-- 通知历史 -->
    <section class="notification-history">
        <h2>通知历史</h2>
        <div class="history-controls">
            <button id="clear-history">清空历史</button>
            <button id="close-all">关闭所有通知</button>
        </div>
        <div id="history-list" role="log" aria-live="polite"></div>
    </section>
    
    <script>
        // Notification API实现
        document.addEventListener('DOMContentLoaded', function() {
            const permissionStatus = document.getElementById('permission-status');
            const titleInput = document.getElementById('title-input');
            const bodyInput = document.getElementById('body-input');
            const iconInput = document.getElementById('icon-input');
            const imageInput = document.getElementById('image-input');
            const tagInput = document.getElementById('tag-input');
            const badgeInput = document.getElementById('badge-input');
            const requireInteractionCheckbox = document.getElementById('require-interaction');
            const silentCheckbox = document.getElementById('silent');
            const historyList = document.getElementById('history-list');
            
            let notificationHistory = [];
            let activeNotifications = [];
            
            // 初始化
            checkNotificationSupport();
            updatePermissionStatus();
            
            // 权限请求
            document.getElementById('request-permission').addEventListener('click', requestPermission);
            
            // 通知发送
            document.getElementById('send-basic').addEventListener('click', sendBasicNotification);
            document.getElementById('send-advanced').addEventListener('click', sendAdvancedNotification);
            
            // 模板通知
            document.getElementById('welcome-notification').addEventListener('click', () => {
                sendTemplateNotification('welcome');
            });
            
            document.getElementById('reminder-notification').addEventListener('click', () => {
                sendTemplateNotification('reminder');
            });
            
            document.getElementById('update-notification').addEventListener('click', () => {
                sendTemplateNotification('update');
            });
            
            document.getElementById('error-notification').addEventListener('click', () => {
                sendTemplateNotification('error');
            });
            
            // 历史管理
            document.getElementById('clear-history').addEventListener('click', clearHistory);
            document.getElementById('close-all').addEventListener('click', closeAllNotifications);
            
            // 检查通知支持
            function checkNotificationSupport() {
                if (!('Notification' in window)) {
                    permissionStatus.textContent = '不支持通知API';
                    permissionStatus.style.color = 'red';
                    return false;
                }
                return true;
            }
            
            // 更新权限状态
            function updatePermissionStatus() {
                if (!checkNotificationSupport()) return;
                
                const permission = Notification.permission;
                permissionStatus.textContent = getPermissionText(permission);
                permissionStatus.style.color = getPermissionColor(permission);
            }
            
            // 获取权限文本
            function getPermissionText(permission) {
                switch (permission) {
                    case 'granted': return '已授权';
                    case 'denied': return '已拒绝';
                    case 'default': return '未请求';
                    default: return '未知';
                }
            }
            
            // 获取权限颜色
            function getPermissionColor(permission) {
                switch (permission) {
                    case 'granted': return 'green';
                    case 'denied': return 'red';
                    case 'default': return 'orange';
                    default: return 'gray';
                }
            }
            
            // 请求权限
            function requestPermission() {
                if (!checkNotificationSupport()) return;
                
                Notification.requestPermission().then(permission => {
                    updatePermissionStatus();
                    
                    if (permission === 'granted') {
                        addToHistory('权限已授权', '用户授权了通知权限');
                    } else if (permission === 'denied') {
                        addToHistory('权限被拒绝', '用户拒绝了通知权限');
                    }
                });
            }
            
            // 发送基本通知
            function sendBasicNotification() {
                if (!checkPermission()) return;
                
                const title = titleInput.value.trim() || '默认标题';
                const body = bodyInput.value.trim() || '默认内容';
                
                const notification = new Notification(title, {
                    body: body
                });
                
                handleNotificationEvents(notification, title, body);
                addToHistory(title, body);
            }
            
            // 发送高级通知
            function sendAdvancedNotification() {
                if (!checkPermission()) return;
                
                const title = titleInput.value.trim() || '默认标题';
                const options = {
                    body: bodyInput.value.trim() || '默认内容',
                    icon: iconInput.value.trim(),
                    image: imageInput.value.trim(),
                    badge: badgeInput.value.trim(),
                    tag: tagInput.value.trim(),
                    requireInteraction: requireInteractionCheckbox.checked,
                    silent: silentCheckbox.checked,
                    timestamp: Date.now(),
                    data: {
                        customData: '自定义数据',
                        url: window.location.href
                    }
                };
                
                // 清理空值
                Object.keys(options).forEach(key => {
                    if (options[key] === '' || options[key] === null) {
                        delete options[key];
                    }
                });
                
                const notification = new Notification(title, options);
                
                handleNotificationEvents(notification, title, options.body);
                addToHistory(title, options.body, 'advanced');
            }
            
            // 发送模板通知
            function sendTemplateNotification(type) {
                if (!checkPermission()) return;
                
                const templates = {
                    welcome: {
                        title: '欢迎使用我们的应用!',
                        body: '感谢您的注册,开始探索精彩功能吧!',
                        icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="green" d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',
                        tag: 'welcome'
                    },
                    reminder: {
                        title: '提醒',
                        body: '您有一个重要任务需要完成',
                        icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="orange" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
                        tag: 'reminder',
                        requireInteraction: true
                    },
                    update: {
                        title: '应用更新',
                        body: '新版本已可用,包含性能改进和新功能',
                        icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="blue" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
                        tag: 'update'
                    },
                    error: {
                        title: '错误通知',
                        body: '操作失败,请重试或联系支持人员',
                        icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="red" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>',
                        tag: 'error',
                        requireInteraction: true
                    }
                };
                
                const template = templates[type];
                if (!template) return;
                
                const notification = new Notification(template.title, template);
                
                handleNotificationEvents(notification, template.title, template.body);
                addToHistory(template.title, template.body, type);
            }
            
            // 处理通知事件
            function handleNotificationEvents(notification, title, body) {
                activeNotifications.push(notification);
                
                notification.onclick = function() {
                    console.log('通知被点击:', title);
                    window.focus();
                    addToHistory('通知点击', `用户点击了通知: ${title}`);
                    notification.close();
                };
                
                notification.onshow = function() {
                    console.log('通知显示:', title);
                };
                
                notification.onclose = function() {
                    console.log('通知关闭:', title);
                    const index = activeNotifications.indexOf(notification);
                    if (index > -1) {
                        activeNotifications.splice(index, 1);
                    }
                };
                
                notification.onerror = function() {
                    console.error('通知错误:', title);
                    addToHistory('通知错误', `通知发送失败: ${title}`);
                };
            }
            
            // 检查权限
            function checkPermission() {
                if (!checkNotificationSupport()) {
                    alert('浏览器不支持通知API');
                    return false;
                }
                
                if (Notification.permission !== 'granted') {
                    alert('请先授权通知权限');
                    return false;
                }
                
                return true;
            }
            
            // 添加到历史记录
            function addToHistory(title, body, type = 'normal') {
                const historyItem = {
                    title: title,
                    body: body,
                    type: type,
                    timestamp: new Date()
                };
                
                notificationHistory.unshift(historyItem);
                
                // 限制历史记录数量
                if (notificationHistory.length > 50) {
                    notificationHistory = notificationHistory.slice(0, 50);
                }
                
                updateHistoryDisplay();
            }
            
            // 更新历史显示
            function updateHistoryDisplay() {
                let historyHtml = '';
                
                notificationHistory.forEach((item, index) => {
                    historyHtml += `
                        <div class="history-item ${item.type}">
                            <div class="history-header">
                                <span class="history-title">${item.title}</span>
                                <span class="history-time">${item.timestamp.toLocaleTimeString()}</span>
                            </div>
                            <div class="history-body">${item.body}</div>
                        </div>
                    `;
                });
                
                historyList.innerHTML = historyHtml || '<p>暂无通知历史</p>';
            }
            
            // 清空历史
            function clearHistory() {
                notificationHistory = [];
                updateHistoryDisplay();
            }
            
            // 关闭所有通知
            function closeAllNotifications() {
                activeNotifications.forEach(notification => {
                    notification.close();
                });
                activeNotifications = [];
                addToHistory('批量操作', '关闭了所有活动通知');
            }
        });
    </script>
</body>
</html>

9.5.5 Device Orientation API

设备方向和运动检测

Device Orientation API提供了访问设备方向和运动传感器的能力。

设备方向检测应用

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Device Orientation API</title>
</head>
<body>
    <h1>设备方向和运动检测</h1>
    
    <!-- 权限状态 -->
    <section class="permission-section">
        <h2>权限状态</h2>
        <div class="permission-info">
            <p>方向权限: <span id="orientation-permission" role="status">检查中...</span></p>
            <p>运动权限: <span id="motion-permission" role="status">检查中...</span></p>
            <button id="request-permissions">请求权限</button>
        </div>
    </section>
    
    <!-- 设备方向显示 -->
    <section class="orientation-section">
        <h2>设备方向</h2>
        <div class="orientation-display">
            <div class="orientation-item">
                <h3>Alpha (Z轴旋转)</h3>
                <div class="value-display">
                    <span id="alpha-value">-</span
                    <div class="visual-indicator">
                        <div id="alpha-indicator" class="indicator"></div>
                    </div>
                </div>
            </div>
            
            <div class="orientation-item">
                <h3>Beta (X轴旋转)</h3>
                <div class="value-display">
                    <span id="beta-value">-</span
                    <div class="visual-indicator">
                        <div id="beta-indicator" class="indicator"></div>
                    </div>
                </div>
            </div>
            
            <div class="orientation-item">
                <h3>Gamma (Y轴旋转)</h3>
                <div class="value-display">
                    <span id="gamma-value">-</span
                    <div class="visual-indicator">
                        <div id="gamma-indicator" class="indicator"></div>
                    </div>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 设备运动显示 -->
    <section class="motion-section">
        <h2>设备运动</h2>
        <div class="motion-display">
            <div class="motion-group">
                <h3>加速度 (m/s²)</h3>
                <div class="motion-values">
                    <p>X: <span id="accel-x">-</span></p>
                    <p>Y: <span id="accel-y">-</span></p>
                    <p>Z: <span id="accel-z">-</span></p>
                </div>
            </div>
            
            <div class="motion-group">
                <h3>重力加速度 (m/s²)</h3>
                <div class="motion-values">
                    <p>X: <span id="gravity-x">-</span></p>
                    <p>Y: <span id="gravity-y">-</span></p>
                    <p>Z: <span id="gravity-z">-</span></p>
                </div>
            </div>
            
            <div class="motion-group">
                <h3>旋转率 (°/s)</h3>
                <div class="motion-values">
                    <p>Alpha: <span id="rotation-alpha">-</span></p>
                    <p>Beta: <span id="rotation-beta">-</span></p>
                    <p>Gamma: <span id="rotation-gamma">-</span></p>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 3D可视化 -->
    <section class="visualization-section">
        <h2>3D可视化</h2>
        <div class="device-model">
            <div id="device-3d" class="device-3d-container">
                <div class="device-cube">
                    <div class="face front">前</div>
                    <div class="face back">后</div>
                    <div class="face right">右</div>
                    <div class="face left">左</div>
                    <div class="face top">上</div>
                    <div class="face bottom">下</div>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 应用示例 -->
    <section class="application-section">
        <h2>应用示例</h2>
        <div class="app-examples">
            <div class="app-item">
                <h3>指南针</h3>
                <div class="compass">
                    <div id="compass-needle" class="compass-needle"></div>
                    <div class="compass-directions">
                        <span class="direction north">N</span>
                        <span class="direction east">E</span>
                        <span class="direction south">S</span>
                        <span class="direction west">W</span>
                    </div>
                </div>
                <p>方向: <span id="compass-direction">-</span></p>
            </div>
            
            <div class="app-item">
                <h3>水平仪</h3>
                <div class="level-tool">
                    <div id="level-bubble" class="level-bubble"></div>
                </div>
                <p>倾斜: <span id="level-angle">-</span>°</p>
            </div>
            
            <div class="app-item">
                <h3>摇晃检测</h3>
                <div class="shake-detector">
                    <div id="shake-indicator" class="shake-indicator">
                        <span id="shake-count">0</span>
                    </div>
                </div>
                <p>摇晃次数: <span id="shake-total">0</span></p>
                <button id="reset-shake">重置</button>
            </div>
        </div>
    </section>
    
    <script>
        // Device Orientation API实现
        document.addEventListener('DOMContentLoaded', function() {
            // 元素引用
            const orientationPermission = document.getElementById('orientation-permission');
            const motionPermission = document.getElementById('motion-permission');
            const alphaValue = document.getElementById('alpha-value');
            const betaValue = document.getElementById('beta-value');
            const gammaValue = document.getElementById('gamma-value');
            const alphaIndicator = document.getElementById('alpha-indicator');
            const betaIndicator = document.getElementById('beta-indicator');
            const gammaIndicator = document.getElementById('gamma-indicator');
            const device3D = document.querySelector('.device-cube');
            const compassNeedle = document.getElementById('compass-needle');
            const compassDirection = document.getElementById('compass-direction');
            const levelBubble = document.getElementById('level-bubble');
            const levelAngle = document.getElementById('level-angle');
            const shakeIndicator = document.getElementById('shake-indicator');
            const shakeCount = document.getElementById('shake-count');
            const shakeTotal = document.getElementById('shake-total');
            
            // 状态变量
            let orientationSupported = false;
            let motionSupported = false;
            let shakeCounter = 0;
            let lastShakeTime = 0;
            let lastAcceleration = { x: 0, y: 0, z: 0 };
            
            // 初始化
            checkSupport();
            setupEventListeners();
            
            // 检查API支持
            function checkSupport() {
                orientationSupported = 'DeviceOrientationEvent' in window;
                motionSupported = 'DeviceMotionEvent' in window;
                
                orientationPermission.textContent = orientationSupported ? '支持' : '不支持';
                motionPermission.textContent = motionSupported ? '支持' : '不支持';
                
                orientationPermission.style.color = orientationSupported ? 'green' : 'red';
                motionPermission.style.color = motionSupported ? 'green' : 'red';
            }
            
            // 设置事件监听
            function setupEventListeners() {
                // 权限请求
                document.getElementById('request-permissions').addEventListener('click', requestPermissions);
                
                // 设备方向事件
                if (orientationSupported) {
                    window.addEventListener('deviceorientation', handleOrientation);
                }
                
                // 设备运动事件
                if (motionSupported) {
                    window.addEventListener('devicemotion', handleMotion);
                }
                
                // 摇晃重置
                document.getElementById('reset-shake').addEventListener('click', resetShake);
            }
            
            // 请求权限
            async function requestPermissions() {
                try {
                    // 请求设备方向权限 (iOS 13+)
                    if (typeof DeviceOrientationEvent.requestPermission === 'function') {
                        const orientationResponse = await DeviceOrientationEvent.requestPermission();
                        orientationPermission.textContent = orientationResponse === 'granted' ? '已授权' : '被拒绝';
                        orientationPermission.style.color = orientationResponse === 'granted' ? 'green' : 'red';
                    } else {
                        orientationPermission.textContent = '自动授权';
                        orientationPermission.style.color = 'green';
                    }
                    
                    // 请求设备运动权限 (iOS 13+)
                    if (typeof DeviceMotionEvent.requestPermission === 'function') {
                        const motionResponse = await DeviceMotionEvent.requestPermission();
                        motionPermission.textContent = motionResponse === 'granted' ? '已授权' : '被拒绝';
                        motionPermission.style.color = motionResponse === 'granted' ? 'green' : 'red';
                    } else {
                        motionPermission.textContent = '自动授权';
                        motionPermission.style.color = 'green';
                    }
                } catch (error) {
                    console.error('权限请求失败:', error);
                }
            }
            
            // 处理设备方向
            function handleOrientation(event) {
                const alpha = event.alpha || 0; // Z轴 (0-360)
                const beta = event.beta || 0;   // X轴 (-180到180)
                const gamma = event.gamma || 0; // Y轴 (-90到90)
                
                // 更新数值显示
                alphaValue.textContent = alpha.toFixed(1);
                betaValue.textContent = beta.toFixed(1);
                gammaValue.textContent = gamma.toFixed(1);
                
                // 更新指示器
                updateIndicator(alphaIndicator, alpha, 360);
                updateIndicator(betaIndicator, beta + 180, 360);
                updateIndicator(gammaIndicator, gamma + 90, 180);
                
                // 更新3D模型
                update3DModel(alpha, beta, gamma);
                
                // 更新指南针
                updateCompass(alpha);
                
                // 更新水平仪
                updateLevel(beta, gamma);
            }
            
            // 处理设备运动
            function handleMotion(event) {
                const acceleration = event.acceleration || {};
                const accelerationIncludingGravity = event.accelerationIncludingGravity || {};
                const rotationRate = event.rotationRate || {};
                
                // 更新加速度显示
                document.getElementById('accel-x').textContent = (acceleration.x || 0).toFixed(2);
                document.getElementById('accel-y').textContent = (acceleration.y || 0).toFixed(2);
                document.getElementById('accel-z').textContent = (acceleration.z || 0).toFixed(2);
                
                // 更新重力加速度显示
                document.getElementById('gravity-x').textContent = (accelerationIncludingGravity.x || 0).toFixed(2);
                document.getElementById('gravity-y').textContent = (accelerationIncludingGravity.y || 0).toFixed(2);
                document.getElementById('gravity-z').textContent = (accelerationIncludingGravity.z || 0).toFixed(2);
                
                // 更新旋转率显示
                document.getElementById('rotation-alpha').textContent = (rotationRate.alpha || 0).toFixed(2);
                document.getElementById('rotation-beta').textContent = (rotationRate.beta || 0).toFixed(2);
                document.getElementById('rotation-gamma').textContent = (rotationRate.gamma || 0).toFixed(2);
                
                // 检测摇晃
                detectShake(accelerationIncludingGravity);
            }
            
            // 更新指示器
            function updateIndicator(indicator, value, max) {
                const percentage = (value / max) * 100;
                indicator.style.transform = `translateX(${percentage}%)`;
            }
            
            // 更新3D模型
            function update3DModel(alpha, beta, gamma) {
                if (device3D) {
                    device3D.style.transform = `
                        rotateZ(${alpha}deg) 
                        rotateX(${beta}deg) 
                        rotateY(${gamma}deg)
                    `;
                }
            }
            
            // 更新指南针
            function updateCompass(alpha) {
                if (compassNeedle) {
                    compassNeedle.style.transform = `rotate(${alpha}deg)`;
                }
                
                // 更新方向文字
                const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
                const index = Math.round(alpha / 45) % 8;
                compassDirection.textContent = directions[index];
            }
            
            // 更新水平仪
            function updateLevel(beta, gamma) {
                if (levelBubble) {
                    const x = Math.max(-30, Math.min(30, gamma)) * 2; // 限制范围并放大
                    const y = Math.max(-30, Math.min(30, beta)) * 2;
                    
                    levelBubble.style.transform = `translate(${x}px, ${y}px)`;
                }
                
                const totalTilt = Math.sqrt(beta * beta + gamma * gamma);
                levelAngle.textContent = totalTilt.toFixed(1);
            }
            
            // 检测摇晃
            function detectShake(acceleration) {
                const threshold = 15; // 摇晃阈值
                const timeThreshold = 200; // 时间阈值 (ms)
                
                const currentTime = Date.now();
                const deltaTime = currentTime - lastShakeTime;
                
                if (deltaTime > timeThreshold) {
                    const deltaX = Math.abs(acceleration.x - lastAcceleration.x);
                    const deltaY = Math.abs(acceleration.y - lastAcceleration.y);
                    const deltaZ = Math.abs(acceleration.z - lastAcceleration.z);
                    
                    const totalDelta = deltaX + deltaY + deltaZ;
                    
                    if (totalDelta > threshold) {
                        shakeCounter++;
                        lastShakeTime = currentTime;
                        
                        // 更新显示
                        shakeCount.textContent = shakeCounter;
                        shakeTotal.textContent = shakeCounter;
                        
                        // 视觉反馈
                        shakeIndicator.style.transform = 'scale(1.2)';
                        shakeIndicator.style.backgroundColor = '#ff6b6b';
                        
                        setTimeout(() => {
                            shakeIndicator.style.transform = 'scale(1)';
                            shakeIndicator.style.backgroundColor = '#4CAF50';
                        }, 200);
                    }
                }
                
                lastAcceleration = {
                    x: acceleration.x || 0,
                    y: acceleration.y || 0,
                    z: acceleration.z || 0
                };
            }
            
            // 重置摇晃计数
            function resetShake() {
                shakeCounter = 0;
                shakeCount.textContent = '0';
                shakeTotal.textContent = '0';
            }
        });
    </script>
    
    <style>
        /* 基本样式 */
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        
        section {
            margin: 20px 0;
            padding: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        
        .orientation-display {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
        }
        
        .orientation-item {
            text-align: center;
        }
        
        .value-display {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 10px;
        }
        
        .visual-indicator {
            width: 200px;
            height: 20px;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 10px;
            position: relative;
            overflow: hidden;
        }
        
        .indicator {
            width: 20px;
            height: 20px;
            background: #4CAF50;
            border-radius: 50%;
            position: absolute;
            top: 0;
            transition: transform 0.1s ease;
        }
        
        /* 3D设备模型 */
        .device-3d-container {
            perspective: 1000px;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 300px;
        }
        
        .device-cube {
            width: 100px;
            height: 100px;
            position: relative;
            transform-style: preserve-3d;
            transition: transform 0.1s ease;
        }
        
        .face {
            position: absolute;
            width: 100px;
            height: 100px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            border: 2px solid white;
        }
        
        .front { background: rgba(255, 0, 0, 0.7); transform: rotateY(0deg) translateZ(50px); }
        .back { background: rgba(0, 255, 0, 0.7); transform: rotateY(180deg) translateZ(50px); }
        .right { background: rgba(0, 0, 255, 0.7); transform: rotateY(90deg) translateZ(50px); }
        .left { background: rgba(255, 255, 0, 0.7); transform: rotateY(-90deg) translateZ(50px); }
        .top { background: rgba(255, 0, 255, 0.7); transform: rotateX(90deg) translateZ(50px); }
        .bottom { background: rgba(0, 255, 255, 0.7); transform: rotateX(-90deg) translateZ(50px); }
        
        /* 指南针 */
        .compass {
            width: 150px;
            height: 150px;
            border: 3px solid white;
            border-radius: 50%;
            position: relative;
            margin: 0 auto;
            background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.3) 100%);
        }
        
        .compass-needle {
            width: 4px;
            height: 60px;
            background: linear-gradient(to top, #ff4444 0%, #ff4444 50%, #4444ff 50%, #4444ff 100%);
            position: absolute;
            top: 20px;
            left: 50%;
            transform-origin: bottom center;
            transform: translateX(-50%);
            transition: transform 0.1s ease;
        }
        
        .compass-directions {
            position: absolute;
            width: 100%;
            height: 100%;
        }
        
        .direction {
            position: absolute;
            font-weight: bold;
            font-size: 18px;
        }
        
        .north { top: 5px; left: 50%; transform: translateX(-50%); }
        .east { right: 10px; top: 50%; transform: translateY(-50%); }
        .south { bottom: 5px; left: 50%; transform: translateX(-50%); }
        .west { left: 10px; top: 50%; transform: translateY(-50%); }
        
        /* 水平仪 */
        .level-tool {
            width: 200px;
            height: 200px;
            border: 3px solid white;
            border-radius: 50%;
            position: relative;
            margin: 0 auto;
            background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.3) 100%);
        }
        
        .level-bubble {
            width: 30px;
            height: 30px;
            background: #4CAF50;
            border-radius: 50%;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            transition: transform 0.1s ease;
            box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
        }
        
        /* 摇晃检测器 */
        .shake-indicator {
            width: 100px;
            height: 100px;
            border-radius: 50%;
            background: #4CAF50;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24px;
            font-weight: bold;
            margin: 0 auto;
            transition: all 0.2s ease;
        }
        
        .app-examples {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
        }
        
        .app-item {
            text-align: center;
            padding: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
        }
        
        .motion-display {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
        }
        
        .motion-group {
            text-align: center;
            padding: 15px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
        }
        
        button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin: 5px;
        }
        
        button:hover {
            background: #45a049;
        }
        
        input[type="url"], input[type="text"], textarea {
            width: 100%;
            padding: 10px;
            border: none;
            border-radius: 5px;
            margin: 5px 0;
            box-sizing: border-box;
        }
    </style>
</body>
</html>

9.5.6 综合API应用示例

多API集成应用

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5 API综合应用</title>
</head>
<body>
    <h1>HTML5 API综合应用示例</h1>
    
    <!-- 应用导航 -->
    <nav class="app-navigation">
        <button class="nav-tab active" data-tab="dashboard">仪表板</button>
        <button class="nav-tab" data-tab="file-manager">文件管理</button>
        <button class="nav-tab" data-tab="chat-room">聊天室</button>
        <button class="nav-tab" data-tab="device-monitor">设备监控</button>
    </nav>
    
    <!-- 仪表板 -->
    <section id="dashboard" class="tab-content active">
        <h2>系统仪表板</h2>
        <div class="dashboard-grid">
            <div class="widget">
                <h3>当前位置</h3>
                <div id="location-info">获取中...</div>
                <button id="get-location">获取位置</button>
            </div>
            
            <div class="widget">
                <h3>设备状态</h3>
                <div id="device-status">
                    <p>方向: <span id="device-orientation">-</span></p>
                    <p>电池: <span id="battery-status">-</span></p>
                    <p>网络: <span id="network-status">-</span></p>
                </div>
            </div>
            
            <div class="widget">
                <h3>通知中心</h3>
                <div id="notification-center">
                    <button id="test-notification">测试通知</button>
                    <p>通知状态: <span id="notification-status">-</span></p>
                </div>
            </div>
        </div>
    </section>
    
    <!-- 文件管理器 -->
    <section id="file-manager" class="tab-content">
        <h2>文件管理器</h2>
        <div class="file-manager-container">
            <div class="file-upload-area">
                <input type="file" id="file-upload" multiple>
                <div id="drop-area" class="drop-area">
                    拖放文件到此区域
                </div>
            </div>
            
            <div class="file-list">
                <h3>已上传文件</h3>
                <div id="uploaded-files"></div>
            </div>
            
            <div class="file-operations">
                <button id="download-all">下载所有文件</button>
                <button id="clear-files">清空文件</button>
            </div>
        </div>
    </section>
    
    <!-- 聊天室 -->
    <section id="chat-room" class="tab-content">
        <h2>实时聊天室</h2>
        <div class="chat-container">
            <div class="connection-controls">
                <input type="url" id="chat-server" placeholder="WebSocket服务器地址" value="wss://echo.websocket.org">
                <button id="connect-chat">连接</button>
                <button id="disconnect-chat">断开</button>
                <span id="chat-status">未连接</span>
            </div>
            
            <div id="chat-messages" class="chat-messages"></div>
            
            <div class="message-input-area">
                <input type="text" id="message-input" placeholder="输入消息...">
                <button id="send-message">发送</button>
                <button id="send-file-message">发送文件</button>
            </div>
        </div>
    </section>
    
    <!-- 设备监控 -->
    <section id="device-monitor" class="tab-content">
        <h2>设备监控</h2>
        <div class="monitor-grid">
            <div class="monitor-widget">
                <h3>设备方向</h3>
                <div id="orientation-display">
                    <canvas id="orientation-canvas" width="200" height="200"></canvas>
                </div>
            </div>
            
            <div class="monitor-widget">
                <h3>运动检测</h3>
                <div id="motion-display">
                    <p>加速度: <span id="acceleration-info">-</span></p>
                    <p>摇晃次数: <span id="shake-count-display">0</span></p>
                    <button id="reset-motion">重置</button>
                </div>
            </div>
            
            <div class="monitor-widget">
                <h3>环境信息</h3>
                <div id="environment-info">
                    <p>时区: <span id="timezone-info">-</span></p>
                    <p>语言: <span id="language-info">-</span></p>
                    <p>屏幕: <span id="screen-info">-</span></p>
                </div>
            </div>
        </div>
    </section>
    
    <script>
        // HTML5 API综合应用实现
        class IntegratedApp {
            constructor() {
                this.websocket = null;
                this.uploadedFiles = [];
                this.shakeCount = 0;
                this.orientationCanvas = null;
                this.orientationCtx = null;
                
                this.init();
            }
            
            init() {
                this.setupNavigation();
                this.setupDashboard();
                this.setupFileManager();
                this.setupChatRoom();
                this.setupDeviceMonitor();
                this.loadSystemInfo();
            }
            
            // 设置导航
            setupNavigation() {
                const navTabs = document.querySelectorAll('.nav-tab');
                const tabContents = document.querySelectorAll('.tab-content');
                
                navTabs.forEach(tab => {
                    tab.addEventListener('click', () => {
                        const targetTab = tab.dataset.tab;
                        
                        // 更新导航状态
                        navTabs.forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        
                        // 显示对应内容
                        tabContents.forEach(content => {
                            content.classList.remove('active');
                            if (content.id === targetTab) {
                                content.classList.add('active');
                            }
                        });
                        
                        // 记录导航历史
                        history.pushState({ tab: targetTab }, '', `#${targetTab}`);
                    });
                });
                
                // 处理浏览器后退/前进
                window.addEventListener('popstate', (e) => {
                    if (e.state && e.state.tab) {
                        this.switchToTab(e.state.tab);
                    }
                });
            }
            
            // 设置仪表板
            setupDashboard() {
                // 地理定位
                document.getElementById('get-location').addEventListener('click', () => {
                    this.getCurrentLocation();
                });
                
                // 测试通知
                document.getElementById('test-notification').addEventListener('click', () => {
                    this.sendTestNotification();
                });
                
                // 检查通知权限
                this.checkNotificationPermission();
            }
            
            // 设置文件管理器
            setupFileManager() {
                const fileUpload = document.getElementById('file-upload');
                const dropArea = document.getElementById('drop-area');
                
                fileUpload.addEventListener('change', (e) => {
                    this.handleFileUpload(e.target.files);
                });
                
                // 拖放事件
                dropArea.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    dropArea.classList.add('dragover');
                });
                
                dropArea.addEventListener('dragleave', () => {
                    dropArea.classList.remove('dragover');
                });
                
                dropArea.addEventListener('drop', (e) => {
                    e.preventDefault();
                    dropArea.classList.remove('dragover');
                    this.handleFileUpload(e.dataTransfer.files);
                });
                
                // 文件操作
                document.getElementById('download-all').addEventListener('click', () => {
                    this.downloadAllFiles();
                });
                
                document.getElementById('clear-files').addEventListener('click', () => {
                    this.clearAllFiles();
                });
            }
            
            // 设置聊天室
            setupChatRoom() {
                document.getElementById('connect-chat').addEventListener('click', () => {
                    this.connectToChat();
                });
                
                document.getElementById('disconnect-chat').addEventListener('click', () => {
                    this.disconnectFromChat();
                });
                
                document.getElementById('send-message').addEventListener('click', () => {
                    this.sendChatMessage();
                });
                
                document.getElementById('message-input').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') {
                        this.sendChatMessage();
                    }
                });
            }
            
            // 设置设备监控
            setupDeviceMonitor() {
                this.orientationCanvas = document.getElementById('orientation-canvas');
                this.orientationCtx = this.orientationCanvas.getContext('2d');
                
                // 设备方向监听
                if ('DeviceOrientationEvent' in window) {
                    window.addEventListener('deviceorientation', (e) => {
                        this.updateOrientationDisplay(e.alpha, e.beta, e.gamma);
                    });
                }
                
                // 设备运动监听
                if ('DeviceMotionEvent' in window) {
                    window.addEventListener('devicemotion', (e) => {
                        this.updateMotionDisplay(e);
                    });
                }
                
                document.getElementById('reset-motion').addEventListener('click', () => {
                    this.shakeCount = 0;
                    document.getElementById('shake-count-display').textContent = '0';
                });
            }
            
            // 获取当前位置
            getCurrentLocation() {
                if ('geolocation' in navigator) {
                    navigator.geolocation.getCurrentPosition(
                        (position) => {
                            const { latitude, longitude } = position.coords;
                            document.getElementById('location-info').innerHTML = `
                                <p>纬度: ${latitude.toFixed(6)}</p>
                                <p>经度: ${longitude.toFixed(6)}</p>
                                <p>精度: ${position.coords.accuracy}m</p>
                            `;
                        },
                        (error) => {
                            document.getElementById('location-info').textContent = '获取位置失败: ' + error.message;
                        }
                    );
                } else {
                    document.getElementById('location-info').textContent = '不支持地理定位';
                }
            }
            
            // 检查通知权限
            checkNotificationPermission() {
                if ('Notification' in window) {
                    document.getElementById('notification-status').textContent = Notification.permission;
                } else {
                    document.getElementById('notification-status').textContent = '不支持';
                }
            }
            
            // 发送测试通知
            sendTestNotification() {
                if ('Notification' in window) {
                    if (Notification.permission === 'granted') {
                        new Notification('测试通知', {
                            body: '这是一个测试通知',
                            icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="blue" d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>'
                        });
                    } else if (Notification.permission !== 'denied') {
                        Notification.requestPermission().then(permission => {
                            this.checkNotificationPermission();
                            if (permission === 'granted') {
                                this.sendTestNotification();
                            }
                        });
                    }
                }
            }
            
            // 处理文件上传
            handleFileUpload(files) {
                Array.from(files).forEach(file => {
                    this.uploadedFiles.push(file);
                    this.displayUploadedFile(file);
                });
            }
            
            // 显示上传的文件
            displayUploadedFile(file) {
                const fileList = document.getElementById('uploaded-files');
                const fileItem = document.createElement('div');
                fileItem.className = 'file-item';
                fileItem.innerHTML = `
                    <span>${file.name} (${this.formatFileSize(file.size)})</span>
                    <button onclick="this.parentElement.remove()">删除</button>
                `;
                fileList.appendChild(fileItem);
            }
            
            // 连接聊天
            connectToChat() {
                const serverUrl = document.getElementById('chat-server').value;
                
                try {
                    this.websocket = new WebSocket(serverUrl);
                    
                    this.websocket.onopen = () => {
                        document.getElementById('chat-status').textContent = '已连接';
                        this.addChatMessage('系统', '连接成功');
                    };
                    
                    this.websocket.onmessage = (e) => {
                        this.addChatMessage('服务器', e.data);
                    };
                    
                    this.websocket.onclose = () => {
                        document.getElementById('chat-status').textContent = '已断开';
                        this.addChatMessage('系统', '连接断开');
                    };
                    
                } catch (error) {
                    this.addChatMessage('错误', '连接失败: ' + error.message);
                }
            }
            
            // 发送聊天消息
            sendChatMessage() {
                const messageInput = document.getElementById('message-input');
                const message = messageInput.value.trim();
                
                if (this.websocket && this.websocket.readyState === WebSocket.OPEN && message) {
                    this.websocket.send(message);
                    this.addChatMessage('我', message);
                    messageInput.value = '';
                }
            }
            
            // 添加聊天消息
            addChatMessage(sender, message) {
                const chatMessages = document.getElementById('chat-messages');
                const messageElement = document.createElement('div');
                messageElement.className = 'chat-message';
                messageElement.innerHTML = `
                    <span class="sender">${sender}:</span>
                    <span class="message">${message}</span>
                    <span class="time">${new Date().toLocaleTimeString()}</span>
                `;
                chatMessages.appendChild(messageElement);
                chatMessages.scrollTop = chatMessages.scrollHeight;
            }
            
            // 更新方向显示
            updateOrientationDisplay(alpha, beta, gamma) {
                if (!this.orientationCtx) return;
                
                const ctx = this.orientationCtx;
                const canvas = this.orientationCanvas;
                
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                // 绘制指南针
                const centerX = canvas.width / 2;
                const centerY = canvas.height / 2;
                const radius = 80;
                
                // 外圆
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
                ctx.strokeStyle = '#fff';
                ctx.lineWidth = 2;
                ctx.stroke();
                
                // 指针
                const angle = (alpha || 0) * Math.PI / 180;
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.lineTo(
                    centerX + Math.sin(angle) * radius * 0.8,
                    centerY - Math.cos(angle) * radius * 0.8
                );
                ctx.strokeStyle = '#ff4444';
                ctx.lineWidth = 3;
                ctx.stroke();
                
                // 方向标记
                ctx.fillStyle = '#fff';
                ctx.font = '16px Arial';
                ctx.textAlign = 'center';
                ctx.fillText('N', centerX, centerY - radius - 15);
            }
            
            // 加载系统信息
            loadSystemInfo() {
                // 设备方向
                document.getElementById('device-orientation').textContent = screen.orientation ? screen.orientation.angle + '°' : '未知';
                
                // 网络状态
                document.getElementById('network-status').textContent = navigator.onLine ? '在线' : '离线';
                
                // 时区信息
                document.getElementById('timezone-info').textContent = Intl.DateTimeFormat().resolvedOptions().timeZone;
                
                // 语言信息
                document.getElementById('language-info').textContent = navigator.language;
                
                // 屏幕信息
                document.getElementById('screen-info').textContent = `${screen.width}x${screen.height}`;
            }
            
            // 格式化文件大小
            formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }
        }
        
        // 启动应用
        document.addEventListener('DOMContentLoaded', () => {
            new IntegratedApp();
        });
    </script>
    
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            min-height: 100vh;
            padding: 20px;
        }
        
        .app-navigation {
            display: flex;
            gap: 10px;
            margin-bottom: 30px;
            background: rgba(255, 255, 255, 0.1);
            padding: 10px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        
        .nav-tab {
            padding: 10px 20px;
            background: transparent;
            border: 2px solid rgba(255, 255, 255, 0.3);
            color: white;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .nav-tab.active, .nav-tab:hover {
            background: rgba(255, 255, 255, 0.2);
            border-color: white;
        }
        
        .tab-content {
            display: none;
            background: rgba(255, 255, 255, 0.1);
            padding: 30px;
            border-radius: 15px;
            backdrop-filter: blur(10px);
        }
        
        .tab-content.active {
            display: block;
        }
        
        .dashboard-grid, .monitor-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-top: 20px;
        }
        
        .widget, .monitor-widget {
            background: rgba(255, 255, 255, 0.1);
            padding: 20px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        
        .file-manager-container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }
        
        .drop-area {
            border: 2px dashed rgba(255, 255, 255, 0.5);
            padding: 40px;
            text-align: center;
            border-radius: 10px;
            transition: all 0.3s ease;
        }
        
        .drop-area.dragover {
            border-color: #4CAF50;
            background: rgba(76, 175, 80, 0.1);
        }
        
        .chat-container {
            display: flex;
            flex-direction: column;
            height: 500px;
        }
        
        .chat-messages {
            flex: 1;
            background: rgba(0, 0, 0, 0.3);
            padding: 20px;
            border-radius: 10px;
            overflow-y: auto;
            margin: 10px 0;
        }
        
        .chat-message {
            margin: 10px 0;
            padding: 10px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 5px;
        }
        
        .message-input-area {
            display: flex;
            gap: 10px;
        }
        
        input, button, textarea {
            padding: 10px;
            border: none;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.9);
            color: #333;
        }
        
        button {
            background: #4CAF50;
            color: white;
            cursor: pointer;
            transition: background 0.3s ease;
        }
        
        button:hover {
            background: #45a049;
        }
        
        input[type="text"], input[type="url"], textarea {
            flex: 1;
        }
        
        #orientation-canvas {
            background: rgba(0, 0, 0, 0.3);
            border-radius: 10px;
        }
        
        .file-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            margin: 5px 0;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 5px;
        }
    </style>
</body>
</html>

关键知识点总结

1. History API核心功能

  • pushState():添加新的历史记录条目
  • replaceState():替换当前历史记录条目
  • popstate事件:监听浏览器前进后退操作
  • 单页应用路由:实现无刷新页面导航

2. File API主要特性

  • FileReader:读取文件内容的多种方式
  • Blob对象:处理二进制数据
  • 文件拖放:支持拖拽上传文件
  • 文件分片:处理大文件的分块操作

3. WebSocket通信机制

  • 实时双向通信:客户端和服务器实时数据交换
  • 连接状态管理:open、close、error事件处理
  • 消息类型:支持文本和二进制消息
  • 连接控制:手动连接、断开和重连机制

4. Notification API功能

  • 权限管理:请求和检查通知权限
  • 基本通知:标题、内容、图标设置
  • 高级通知:图片、徽章、交互选项
  • 事件处理:点击、显示、关闭事件

5. Device Orientation API特性

  • 方向检测:alpha、beta、gamma三轴旋转
  • 运动检测:加速度、重力、旋转率
  • 权限处理:iOS 13+需要请求权限
  • 实际应用:指南针、水平仪、摇晃检测

常见问题解答

Q1: History API在SEO方面有什么注意事项?

A1: 需要配置服务器支持所有路由,确保搜索引擎爬虫能正确访问各个页面,同时要提供适当的meta标签和结构化数据。

Q2: File API有文件大小限制吗?

A2: 浏览器对File API没有硬性限制,但受内存限制影响。处理大文件时建议使用分片技术。

Q3: WebSocket连接断开后如何自动重连?

A3: 可以实现重连机制,在onclose事件中设置定时器尝试重新连接,并实现指数退避算法避免频繁重连。

Q4: Notification API在不同浏览器中的兼容性如何?

A4: 主流现代浏览器都支持,但某些移动浏览器可能有限制。需要检查支持性并提供降级方案。

Q5: Device Orientation API的精度如何?

A5: 精度取决于设备传感器质量,一般消费级设备精度有限,不适用于需要高精度的应用场景。

学习资源推荐

官方文档

实践项目

  • 单页应用路由系统
  • 在线文件管理器
  • 实时聊天应用
  • 移动端传感器应用

相关技术

  • Service Workers
  • Push API
  • Background Sync
  • Web App Manifest

通过本节的学习,您应该能够熟练使用这些重要的HTML5 API,构建功能丰富的现代Web应用,提供优秀的用户体验。