HTML5 详解速览

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>&copy; 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>&copy; 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 应用。在实际开发中,应该根据项目需求和浏览器兼容性要求选择合适的特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值