HTML5 详解速览
1. HTML5 概述
HTML5 是 HTML 标准的第五个主要版本,于 2014 年正式发布。它引入了许多新特性,旨在改善 Web 应用的功能和用户体验。
主要改进:
- 语义化标签:更清晰的文档结构
- 多媒体支持:原生音频和视频支持
- 表单增强:新的输入类型和属性
- Canvas 绘图:客户端图形渲染
- 本地存储:离线应用支持
- 地理定位:位置服务
- Web Workers:后台线程处理
- WebSocket:实时通信
2. 新的语义化标签
2.1 文档结构标签
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 语义化示例</title>
</head>
<body>
<header>
<h1>网站标题</h1>
<nav>
<ul>
<li><a href="#home">首页</a></li>
<li><a href="#about">关于</a></li>
<li><a href="#contact">联系</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h2>文章标题</h2>
<time datetime="2023-01-01">2023年1月1日</time>
</header>
<section>
<h3>第一部分</h3>
<p>文章内容…</p>
</section>
<section>
<h3>第二部分</h3>
<p>更多内容…</p>
</section>
<footer>
<p>作者:张三</p>
</footer>
</article>
<aside>
<h3>相关文章</h3>
<ul>
<li><a href="#">相关文章1</a></li>
<li><a href="#">相关文章2</a></li>
</ul>
</aside>
</main>
<footer>
<p>© 2023 版权所有</p>
</footer>
</body>
</html>
2.2 详细语义标签说明
<header> – 页眉
<!– 页面头部 –>
<header>
<h1>网站名称</h1>
<nav>…</nav>
</header>
<!– 文章头部 –>
<article>
<header>
<h2>文章标题</h2>
<p>发布时间:<time datetime="2023-01-01">2023-01-01</time></p>
</header>
<p>文章内容…</p>
</article>
<nav> – 导航
<nav>
<ul>
<li><a href="#home">首页</a></li>
<li><a href="#products">产品</a></li>
<li><a href="#services">服务</a></li>
<li><a href="#contact">联系</a></li>
</ul>
</nav>
<main> – 主要内容
<main>
<article>
<h1>主要内容</h1>
<p>这里是页面的主要内容…</p>
</article>
</main>
<article> – 独立文章
<article>
<header>
<h2>博客文章标题</h2>
<p>作者:李四 | 发布时间:2023-01-01</p>
</header>
<p>文章正文…</p>
<footer>
<p>标签:<span class="tag">技术</span> <span class="tag">前端</span></p>
</footer>
</article>
<section> – 章节
<section>
<h2>产品介绍</h2>
<p>产品详细信息…</p>
</section>
<section>
<h2>用户评价</h2>
<p>用户反馈内容…</p>
</section>
<aside> – 侧边栏
<aside>
<h3>最新文章</h3>
<ul>
<li><a href="#">文章标题1</a></li>
<li><a href="#">文章标题2</a></li>
</ul>
</aside>
<aside>
<h3>广告位</h3>
<p>广告内容…</p>
</aside>
<footer> – 页脚
<footer>
<p>© 2023 公司名称. 保留所有权利.</p>
<nav>
<a href="#privacy">隐私政策</a>
<a href="#terms">服务条款</a>
</nav>
</footer>
<figure> 和 <figcaption> – 图文组合
<figure>
<img src="chart.png" alt="销售数据图表">
<figcaption>图1:2023年销售数据趋势</figcaption>
</figure>
<figure>
<video src="demo.mp4" controls></video>
<figcaption>产品演示视频</figcaption>
</figure>
<time> – 时间标记
<time datetime="2023-01-01">2023年1月1日</time>
<time datetime="2023-01-01T14:30">2023年1月1日下午2:30</time>
<time datetime="PT2H30M">2小时30分钟</time>
3. 多媒体元素
3.1 音频 <audio>
<!– 基本音频播放 –>
<audio controls>
<source src="audio.mp3" type="audio/mpeg">
<source src="audio.ogg" type="audio/ogg">
您的浏览器不支持音频播放。
</audio>
<!– 自动播放(需用户交互) –>
<audio controls autoplay muted>
<source src="background.mp3" type="audio/mpeg">
</audio>
<!– 循环播放 –>
<audio controls loop>
<source src="loop.mp3" type="audio/mpeg">
</audio>
<!– 预加载 –>
<audio controls preload="metadata">
<source src="song.mp3" type="audio/mpeg">
</audio>
3.2 视频 <video>
<!– 基本视频播放 –>
<video width="640" height="480" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.webm" type="video/webm">
您的浏览器不支持视频播放。
</video>
<!– 带海报和字幕 –>
<video width="800" height="600" controls poster="poster.jpg">
<source src="movie.mp4" type="video/mp4">
<track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English">
<track src="subtitles_zh.vtt" kind="subtitles" srclang="zh" label="中文">
</video>
<!– 自动播放和静音 –>
<video autoplay muted loop>
<source src="background.mp4" type="video/mp4">
</video>
<!– 内联播放(移动端) –>
<video playsinline controls>
<source src="mobile.mp4" type="video/mp4">
</video>
3.3 媒体属性详解
<video
width="800"
height="600"
controls
autoplay
muted
loop
preload="auto"
poster="thumbnail.jpg">
<source src="video.mp4" type="video/mp4">
<source src="video.webm" type="video/webm">
</video>
属性说明:
- controls:显示播放控件
- autoplay:自动播放(需静音或用户交互)
- muted:静音播放
- loop:循环播放
- preload:预加载策略(none/auto/metadata)
- poster:封面图片
- playsinline:内联播放(移动端)
4. Canvas 绘图
4.1 基本 Canvas 使用
<canvas id="myCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 绘制矩形
ctx.fillStyle = '#FF0000';
ctx.fillRect(10, 10, 100, 100);
// 绘制边框矩形
ctx.strokeStyle = '#0000FF';
ctx.strokeRect(120, 10, 100, 100);
// 绘制圆形
ctx.beginPath();
ctx.arc(200, 200, 50, 0, 2 * Math.PI);
ctx.fillStyle = '#00FF00';
ctx.fill();
ctx.stroke();
// 绘制线条
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(350, 150);
ctx.strokeStyle = '#FF00FF';
ctx.lineWidth = 5;
ctx.stroke();
</script>
4.2 Canvas 高级绘图
<canvas id="advancedCanvas" width="500" height="400"></canvas>
<script>
const canvas = document.getElementById('advancedCanvas');
const ctx = canvas.getContext('2d');
// 渐变填充
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);
// 绘制文本
ctx.font = '30px Arial';
ctx.fillStyle = 'black';
ctx.fillText('Hello Canvas!', 10, 150);
// 绘制描边文本
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeText('Stroke Text', 10, 200);
// 绘制复杂路径
ctx.beginPath();
ctx.moveTo(250, 50);
ctx.lineTo(300, 150);
ctx.lineTo(200, 150);
ctx.closePath();
ctx.fillStyle = 'yellow';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();
// 绘制贝塞尔曲线
ctx.beginPath();
ctx.moveTo(350, 50);
ctx.bezierCurveTo(400, 25, 450, 75, 400, 100);
ctx.strokeStyle = 'green';
ctx.stroke();
</script>
4.3 Canvas 动画示例
<canvas id="animationCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('animationCanvas');
const ctx = canvas.getContext('2d');
let x = 0;
let dx = 2;
function animate() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制移动的矩形
ctx.fillStyle = 'blue';
ctx.fillRect(x, 100, 50, 50);
// 更新位置
x += dx;
// 边界检测
if (x > canvas.width – 50 || x < 0) {
dx = –dx;
}
// 继续动画
requestAnimationFrame(animate);
}
// 开始动画
animate();
</script>
5. 表单增强
5.1 新的输入类型
<form>
<!– 邮箱 –>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<!– 网址 –>
<label for="website">网站:</label>
<input type="url" id="website" name="website">
<!– 数字 –>
<label for="quantity">数量:</label>
<input type="number" id="quantity" name="quantity" min="1" max="100" step="1">
<!– 范围滑块 –>
<label for="range">评分:</label>
<input type="range" id="range" name="range" min="0" max="10" value="5">
<!– 颜色选择器 –>
<label for="color">颜色:</label>
<input type="color" id="color" name="color" value="#ff0000">
<!– 日期 –>
<label for="date">日期:</label>
<input type="date" id="date" name="date">
<!– 时间 –>
<label for="time">时间:</label>
<input type="time" id="time" name="time">
<!– 日期时间 –>
<label for="datetime">日期时间:</label>
<input type="datetime-local" id="datetime" name="datetime">
<!– 月份 –>
<label for="month">月份:</label>
<input type="month" id="month" name="month">
<!– 周 –>
<label for="week">周:</label>
<input type="week" id="week" name="week">
<!– 搜索 –>
<label for="search">搜索:</label>
<input type="search" id="search" name="search">
<!– 电话 –>
<label for="tel">电话:</label>
<input type="tel" id="tel" name="tel">
<!– 提交 –>
<input type="submit" value="提交">
</form>
5.2 表单属性增强
<form>
<!– 自动聚焦 –>
<input type="text" name="username" autofocus>
<!– 占位符 –>
<input type="text" name="search" placeholder="请输入搜索关键词">
<!– 必填 –>
<input type="text" name="required" required>
<!– 禁用自动完成 –>
<input type="text" name="credit-card" autocomplete="off">
<!– 自动纠正 –>
<input type="text" name="message" autocorrect="on">
<!– 拼写检查 –>
<input type="text" name="comment" spellcheck="true">
<!– 最小最大长度 –>
<input type="text" name="username" minlength="3" maxlength="20">
<!– 模式匹配 –>
<input type="text" name="phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
placeholder="123-456-7890">
<!– 多行文本域增强 –>
<textarea name="message" rows="5" cols="50"
placeholder="请输入您的留言…"
maxlength="500"></textarea>
</form>
5.3 表单验证
<form id="validationForm">
<!– 必填验证 –>
<label for="name">姓名(必填):</label>
<input type="text" id="name" name="name" required>
<!– 邮箱验证 –>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
<!– 数字范围验证 –>
<label for="age">年龄(18-100):</label>
<input type="number" id="age" name="age" min="18" max="100" required>
<!– 自定义验证 –>
<label for="password">密码(至少8位):</label>
<input type="password" id="password" name="password"
minlength="8" required>
<!– 确认密码 –>
<label for="confirmPassword">确认密码:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
<!– 自定义验证脚本 –>
<script>
document.getElementById('validationForm').addEventListener('submit', function(e) {
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (password !== confirmPassword) {
e.preventDefault();
alert('密码不匹配!');
return false;
}
// 自定义验证
if (password.length < 8) {
e.preventDefault();
alert('密码至少需要8位!');
return false;
}
});
</script>
<input type="submit" value="注册">
</form>
6. 本地存储
6.1 localStorage
// 存储数据
localStorage.setItem('username', 'John Doe');
localStorage.setItem('theme', 'dark');
localStorage.setItem('preferences', JSON.stringify({
language: 'zh-CN',
notifications: true
}));
// 读取数据
const username = localStorage.getItem('username');
const theme = localStorage.getItem('theme');
const preferences = JSON.parse(localStorage.getItem('preferences') || '{}');
// 删除数据
localStorage.removeItem('username');
// 清空所有数据
localStorage.clear();
// 监听存储变化
window.addEventListener('storage', function(e) {
console.log('存储变化:', e.key, e.newValue, e.oldValue);
});
6.2 sessionStorage
// sessionStorage 只在当前会话期间有效
sessionStorage.setItem('sessionId', 'abc123');
sessionStorage.setItem('currentPage', 'dashboard');
const sessionId = sessionStorage.getItem('sessionId');
const currentPage = sessionStorage.getItem('currentPage');
// 页面关闭时自动清除
6.3 存储工具类
// 本地存储工具类
class StorageManager {
static set(key, value) {
try {
const serializedValue = typeof value === 'object'
? JSON.stringify(value)
: value;
localStorage.setItem(key, serializedValue);
return true;
} catch (error) {
console.error('存储失败:', error);
return false;
}
}
static get(key) {
try {
const value = localStorage.getItem(key);
if (value === null) return null;
try {
return JSON.parse(value);
} catch {
return value;
}
} catch (error) {
console.error('读取失败:', error);
return null;
}
}
static remove(key) {
try {
localStorage.removeItem(key);
return true;
} catch (error) {
console.error('删除失败:', error);
return false;
}
}
static clear() {
try {
localStorage.clear();
return true;
} catch (error) {
console.error('清空失败:', error);
return false;
}
}
static has(key) {
return localStorage.getItem(key) !== null;
}
}
// 使用示例
StorageManager.set('user', { name: 'John', age: 30 });
const user = StorageManager.get('user');
console.log(user); // { name: 'John', age: 30 }
7. 地理定位
7.1 基本地理定位
// 检查地理定位支持
if (navigator.geolocation) {
// 获取当前位置
navigator.geolocation.getCurrentPosition(
function(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const accuracy = position.coords.accuracy;
console.log('纬度:', latitude);
console.log('经度:', longitude);
console.log('精度:', accuracy, '米');
// 显示位置信息
displayLocation(latitude, longitude);
},
function(error) {
switch(error.code) {
case error.PERMISSION_DENIED:
console.error("用户拒绝了地理定位请求");
break;
case error.POSITION_UNAVAILABLE:
console.error("位置信息不可用");
break;
case error.TIMEOUT:
console.error("请求超时");
break;
case error.UNKNOWN_ERROR:
console.error("未知错误");
break;
}
},
{
enableHighAccuracy: true, // 高精度
timeout: 10000, // 超时时间
maximumAge: 60000 // 缓存时间
}
);
} else {
console.error("浏览器不支持地理定位");
}
function displayLocation(lat, lng) {
// 在地图上显示位置
const mapUrl = `https://maps.google.com/maps?q=${lat},${lng}&output=embed`;
document.getElementById('map').innerHTML =
`<iframe src="${mapUrl}" width="100%" height="400"></iframe>`;
}
7.2 持续位置监控
// 持续监控位置变化
let watchId;
function startWatching() {
if (navigator.geolocation) {
watchId = navigator.geolocation.watchPosition(
function(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
// 更新位置显示
updateLocationDisplay(lat, lng);
// 检查是否到达目的地
checkDestination(lat, lng);
},
function(error) {
console.error('位置监控错误:', error);
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 1000
}
);
}
}
function stopWatching() {
if (watchId) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
}
}
function updateLocationDisplay(lat, lng) {
document.getElementById('latitude').textContent = lat.toFixed(6);
document.getElementById('longitude').textContent = lng.toFixed(6);
}
function checkDestination(currentLat, currentLng) {
const destinationLat = 39.9042;
const destinationLng = 116.4074;
const distance = calculateDistance(
currentLat, currentLng,
destinationLat, destinationLng
);
if (distance < 100) { // 100米内
alert('您已到达目的地!');
stopWatching();
}
}
// 计算两点间距离(米)
function calculateDistance(lat1, lng1, lat2, lng2) {
const R = 6371e3; // 地球半径(米)
const φ1 = lat1 * Math.PI/180;
const φ2 = lat2 * Math.PI/180;
const Δφ = (lat2–lat1) * Math.PI/180;
const Δλ = (lng2–lng1) * Math.PI/180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1–a));
return R * c;
}
8. Web Workers
8.1 基本 Web Worker
// main.js – 主线程
const worker = new Worker('worker.js');
// 向 Worker 发送消息
worker.postMessage({ command: 'calculate', number: 1000000 });
// 接收 Worker 消息
worker.onmessage = function(e) {
console.log('计算结果:', e.data.result);
console.log('耗时:', e.data.time, 'ms');
};
// 处理 Worker 错误
worker.onerror = function(error) {
console.error('Worker 错误:', error);
};
// 终止 Worker
// worker.terminate();
// worker.js – Worker 线程
self.onmessage = function(e) {
const { command, number } = e.data;
if (command === 'calculate') {
const startTime = Date.now();
let result = 0;
// 执行耗时计算
for (let i = 0; i < number; i++) {
result += Math.sqrt(i);
}
const endTime = Date.now();
// 发送结果回主线程
self.postMessage({
result: result,
time: endTime – startTime
});
}
};
// Worker 也可以监听错误
self.onerror = function(error) {
console.error('Worker 内部错误:', error);
};
8.2 复杂 Worker 示例
// imageProcessor.js – 图像处理 Worker
self.importScripts('utils.js');
self.onmessage = function(e) {
const { imageData, operation } = e.data;
switch(operation) {
case 'grayscale':
processGrayscale(imageData);
break;
case 'blur':
processBlur(imageData);
break;
case 'brightness':
processBrightness(imageData, 50);
break;
}
self.postMessage({ imageData: imageData });
};
function processGrayscale(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
}
function processBlur(imageData) {
// 实现模糊算法
// …
}
// 主线程中使用图像处理 Worker
const imageWorker = new Worker('imageProcessor.js');
function processImage(canvas, operation) {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 发送图像数据到 Worker
imageWorker.postMessage({
imageData: imageData,
operation: operation
});
// 接收处理后的图像数据
imageWorker.onmessage = function(e) {
ctx.putImageData(e.data.imageData, 0, 0);
};
}
9. WebSocket 实时通信
9.1 WebSocket 基本使用
// 客户端 WebSocket
class ChatClient {
constructor(url) {
this.url = url;
this.socket = null;
this.connect();
}
connect() {
try {
this.socket = new WebSocket(this.url);
this.socket.onopen = (event) => {
console.log('WebSocket 连接已建立');
this.onConnected();
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onMessage(data);
};
this.socket.onclose = (event) => {
console.log('WebSocket 连接已关闭');
this.onDisconnected();
};
this.socket.onerror = (error) => {
console.error('WebSocket 错误:', error);
this.onError(error);
};
} catch (error) {
console.error('WebSocket 连接失败:', error);
}
}
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
}
}
disconnect() {
if (this.socket) {
this.socket.close();
}
}
onConnected() {
// 连接成功回调
}
onMessage(data) {
// 接收消息回调
console.log('收到消息:', data);
}
onDisconnected() {
// 连接断开回调
}
onError(error) {
// 错误回调
}
}
// 使用示例
const chat = new ChatClient('ws://localhost:8080');
// 发送消息
chat.send({
type: 'message',
content: 'Hello World!',
timestamp: Date.now()
});
9.2 WebSocket 聊天室示例
<!DOCTYPE html>
<html>
<head>
<title>WebSocket 聊天室</title>
<style>
#chatContainer {
width: 500px;
margin: 20px auto;
}
#messages {
height: 300px;
border: 1px solid #ccc;
overflow-y: scroll;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin: 5px 0;
padding: 5px;
border-radius: 3px;
}
.own-message {
background: #e3f2fd;
text-align: right;
}
.other-message {
background: #f5f5f5;
}
#messageForm {
display: flex;
}
#messageInput {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
}
#sendButton {
padding: 10px 20px;
background: #2196f3;
color: white;
border: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="chatContainer">
<div id="messages"></div>
<form id="messageForm">
<input type="text" id="messageInput" placeholder="输入消息…" required>
<button type="submit" id="sendButton">发送</button>
</form>
</div>
<script>
class ChatRoom {
constructor() {
this.socket = null;
this.username = 'User' + Math.floor(Math.random() * 1000);
this.init();
}
init() {
this.connect();
this.setupEventListeners();
}
connect() {
try {
this.socket = new WebSocket('ws://localhost:8080');
this.socket.onopen = () => {
console.log('连接成功');
this.addSystemMessage('已连接到聊天室');
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.displayMessage(data);
};
this.socket.onclose = () => {
this.addSystemMessage('连接已断开');
};
this.socket.onerror = (error) => {
console.error('连接错误:', error);
this.addSystemMessage('连接错误');
};
} catch (error) {
console.error('连接失败:', error);
}
}
setupEventListeners() {
const form = document.getElementById('messageForm');
const input = document.getElementById('messageInput');
form.addEventListener('submit', (e) => {
e.preventDefault();
const message = input.value.trim();
if (message) {
this.sendMessage(message);
input.value = '';
}
});
}
sendMessage(content) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const message = {
type: 'chat',
username: this.username,
content: content,
timestamp: Date.now()
};
this.socket.send(JSON.stringify(message));
}
}
displayMessage(data) {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
if (data.type === 'system') {
messageDiv.className = 'message system-message';
messageDiv.innerHTML = `<em>${data.content}</em>`;
} else {
const isOwn = data.username === this.username;
messageDiv.className = `message ${isOwn ? 'own-message' : 'other-message'}`;
messageDiv.innerHTML = `
<strong>${data.username}:</strong> ${data.content}
<small style="display: block; font-size: 0.8em; color: #666;">
${new Date(data.timestamp).toLocaleTimeString()}
</small>
`;
}
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
addSystemMessage(content) {
this.displayMessage({
type: 'system',
content: content,
timestamp: Date.now()
});
}
}
// 初始化聊天室
const chatRoom = new ChatRoom();
</script>
</body>
</html>
10. 拖放 API
10.1 基本拖放功能
<!DOCTYPE html>
<html>
<head>
<style>
.draggable {
width: 100px;
height: 100px;
background: #4CAF50;
margin: 10px;
cursor: move;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.drop-zone {
width: 300px;
height: 200px;
border: 2px dashed #ccc;
margin: 20px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.drop-zone.drag-over {
border-color: #4CAF50;
background: #e8f5e8;
}
.drop-zone.dropped {
border-color: #2196F3;
background: #e3f2fd;
}
</style>
</head>
<body>
<div class="draggable" draggable="true" data-type="square">正方形</div>
<div class="draggable" draggable="true" data-type="circle">圆形</div>
<div class="draggable" draggable="true" data-type="triangle">三角形</div>
<div class="drop-zone" id="dropZone1">拖放到这里</div>
<div class="drop-zone" id="dropZone2">拖放到这里</div>
<script>
// 拖拽元素
const draggables = document.querySelectorAll('.draggable');
const dropZones = document.querySelectorAll('.drop-zone');
// 设置拖拽元素
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', draggable.dataset.type);
e.dataTransfer.effectAllowed = 'move';
draggable.style.opacity = '0.5';
});
draggable.addEventListener('dragend', (e) => {
draggable.style.opacity = '1';
});
});
// 设置放置区域
dropZones.forEach(dropZone => {
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
});
dropZone.addEventListener('dragenter', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', (e) => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const dataType = e.dataTransfer.getData('text/plain');
dropZone.textContent = `放置了: ${dataType}`;
dropZone.classList.add('dropped');
});
});
</script>
</body>
</html>
10.2 文件拖放上传
<!DOCTYPE html>
<html>
<head>
<style>
.upload-area {
width: 400px;
height: 200px;
border: 2px dashed #ccc;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px auto;
transition: all 0.3s;
cursor: pointer;
}
.upload-area.drag-over {
border-color: #4CAF50;
background: #e8f5e8;
transform: scale(1.02);
}
.upload-area:hover {
border-color: #999;
}
.file-list {
width: 400px;
margin: 20px auto;
}
.file-item {
padding: 10px;
border: 1px solid #eee;
margin: 5px 0;
border-radius: 5px;
}
.progress-bar {
width: 100%;
height: 10px;
background: #f0f0f0;
border-radius: 5px;
overflow: hidden;
margin-top: 5px;
}
.progress-fill {
height: 100%;
background: #4CAF50;
width: 0%;
transition: width 0.3s;
}
</style>
</head>
<body>
<div class="upload-area" id="uploadArea">
<div id="uploadText">拖拽文件到这里或点击选择文件</div>
</div>
<input type="file" id="fileInput" multiple style="display: none;">
<div class="file-list" id="fileList"></div>
<script>
class FileUploader {
constructor() {
this.uploadArea = document.getElementById('uploadArea');
this.fileInput = document.getElementById('fileInput');
this.fileList = document.getElementById('fileList');
this.files = [];
this.setupEventListeners();
}
setupEventListeners() {
// 拖拽事件
this.uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
this.uploadArea.classList.add('drag-over');
});
this.uploadArea.addEventListener('dragleave', () => {
this.uploadArea.classList.remove('drag-over');
});
this.uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
this.uploadArea.classList.remove('drag-over');
this.handleFiles(e.dataTransfer.files);
});
// 点击事件
this.uploadArea.addEventListener('click', () => {
this.fileInput.click();
});
this.fileInput.addEventListener('change', (e) => {
this.handleFiles(e.target.files);
});
}
handleFiles(fileList) {
Array.from(fileList).forEach(file => {
this.addFile(file);
});
}
addFile(file) {
const fileId = Date.now() + Math.random();
const fileItem = {
id: fileId,
file: file,
name: file.name,
size: file.size,
type: file.type,
progress: 0
};
this.files.push(fileItem);
this.renderFileItem(fileItem);
this.uploadFile(fileItem);
}
renderFileItem(fileItem) {
const div = document.createElement('div');
div.className = 'file-item';
div.id = `file-${fileItem.id}`;
div.innerHTML = `
<div><strong>${fileItem.name}</strong></div>
<div>大小: ${(fileItem.size / 1024).toFixed(2)} KB</div>
<div>类型: ${fileItem.type || '未知'}</div>
<div class="progress-bar">
<div class="progress-fill" id="progress-${fileItem.id}"></div>
</div>
`;
this.fileList.appendChild(div);
}
updateProgress(fileId, progress) {
const progressFill = document.getElementById(`progress-${fileId}`);
if (progressFill) {
progressFill.style.width = progress + '%';
}
}
async uploadFile(fileItem) {
// 模拟文件上传
const formData = new FormData();
formData.append('file', fileItem.file);
try {
// 模拟上传进度
for (let i = 0; i <= 100; i += 10) {
await this.delay(200);
this.updateProgress(fileItem.id, i);
}
// 模拟上传完成
console.log('文件上传成功:', fileItem.name);
} catch (error) {
console.error('上传失败:', error);
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 初始化文件上传器
const uploader = new FileUploader();
</script>
</body>
</html>
11. 离线应用
11.1 Application Cache (已废弃,推荐使用 Service Worker)
<!– manifest 属性 –>
<html manifest="cache.manifest">
# cache.manifest
CACHE MANIFEST
# 版本 1.0
CACHE:
# 需要缓存的文件
index.html
styles.css
script.js
image.png
NETWORK:
# 需要联网的文件
api/
login
FALLBACK:
# 离线时的替代文件
/ /offline.html
11.2 Service Worker (推荐)
// sw.js – Service Worker
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
'/images/logo.png'
];
// 安装 Service Worker
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('缓存已打开');
return cache.addAll(urlsToCache);
})
);
});
// 拦截网络请求
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// 如果缓存中有,返回缓存的响应
if (response) {
return response;
}
// 否则发起网络请求
return fetch(event.request).then((response) => {
// 检查响应是否有效
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应并缓存
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});
// 更新 Service Worker
self.addEventListener('activate', (event) => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheWhitelist.indexOf(cacheName) === –1) {
return caches.delete(cacheName);
}
})
);
})
);
});
// main.js – 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('Service Worker 注册成功:', registration.scope);
})
.catch((error) => {
console.log('Service Worker 注册失败:', error);
});
});
}
12. 性能优化
12.1 图片懒加载
<!– 使用 loading="lazy" 属性 –>
<img src="image.jpg" alt="图片描述" loading="lazy">
<!– 使用 Intersection Observer API –>
<div class="lazy-image" data-src="image.jpg">
<img src="placeholder.jpg" alt="图片描述">
</div>
<script>
// 自定义懒加载实现
class LazyLoader {
constructor() {
this.imageObserver = null;
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const realSrc = img.dataset.src;
if (realSrc) {
img.src = realSrc;
img.removeAttribute('data-src');
this.imageObserver.unobserve(img);
}
}
});
});
// 观察所有懒加载图片
const lazyImages = document.querySelectorAll('[data-src]');
lazyImages.forEach(img => this.imageObserver.observe(img));
}
}
}
// 初始化懒加载
const lazyLoader = new LazyLoader();
</script>
12.2 预加载和预获取
<!– DNS 预解析 –>
<link rel="dns-prefetch" href="//example.com">
<!– 预连接 –>
<link rel="preconnect" href="https://fonts.googleapis.com">
<!– 预加载关键资源 –>
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="hero-image.jpg" as="image">
<link rel="preload" href="main.js" as="script">
<!– 预获取可能需要的资源 –>
<link rel="prefetch" href="next-page.html">
<link rel="prefetch" href="secondary.js">
<!– 预渲染 –>
<link rel="prerender" href="next-page.html">
12.3 响应式图片
<!– 使用 srcset 和 sizes –>
<img
src="image-400.jpg"
srcset="image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px"
alt="响应式图片">
<!– 使用 picture 元素 –>
<picture>
<source media="(max-width: 600px)" srcset="mobile.jpg">
<source media="(max-width: 1000px)" srcset="tablet.jpg">
<source media="(min-width: 1001px)" srcset="desktop.jpg">
<img src="fallback.jpg" alt="响应式图片">
</picture>
<!– WebP 格式支持 –>
<picture>
<source type="image/webp" srcset="image.webp">
<source type="image/jpeg" srcset="image.jpg">
<img src="image.jpg" alt="支持 WebP 的图片">
</picture>
HTML5 为现代 Web 开发提供了丰富的功能和特性,通过合理使用这些新特性,可以创建更加丰富、交互性强的 Web 应用。在实际开发中,应该根据项目需求和浏览器兼容性要求选择合适的特性。
评论前必须登录!
注册