Search K
Appearance
Appearance
History API, File API, WebSocket, Notification API, Device Orientation API, pushState, FileReader, Blob, ArrayBuffer, notification
HTML5 History API允许JavaScript操作浏览器的历史记录,实现无刷新页面导航。
<!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><!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>HTML5 File API提供了在Web应用中处理文件的能力,包括读取文件内容、获取文件信息等。
<!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><!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>HTML5 Notification API允许网页向用户显示桌面通知,即使浏览器窗口不在前台也能工作。
<!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>Device Orientation API提供了访问设备方向和运动传感器的能力。
<!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><!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>A1: 需要配置服务器支持所有路由,确保搜索引擎爬虫能正确访问各个页面,同时要提供适当的meta标签和结构化数据。
A2: 浏览器对File API没有硬性限制,但受内存限制影响。处理大文件时建议使用分片技术。
A3: 可以实现重连机制,在onclose事件中设置定时器尝试重新连接,并实现指数退避算法避免频繁重连。
A4: 主流现代浏览器都支持,但某些移动浏览器可能有限制。需要检查支持性并提供降级方案。
A5: 精度取决于设备传感器质量,一般消费级设备精度有限,不适用于需要高精度的应用场景。
通过本节的学习,您应该能够熟练使用这些重要的HTML5 API,构建功能丰富的现代Web应用,提供优秀的用户体验。