引言

HTML5作为现代Web开发的基石,不仅定义了网页的结构,还提供了丰富的API来实现复杂的交互功能。从零基础开始学习HTML5前端开发,需要系统地掌握核心技能,并通过实战项目来巩固知识。本文将为你提供一个全面的学习路径,涵盖从基础语法到高级应用,再到实战项目的完整攻略。

第一部分:HTML5基础语法与结构

1.1 HTML5文档结构

HTML5的文档结构比之前的版本更加简洁。一个标准的HTML5文档如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML5页面标题</title>
</head>
<body>
    <!-- 页面内容 -->
</body>
</html>

关键点说明:

  • <!DOCTYPE html>:声明文档类型为HTML5,必须放在文档的第一行。
  • <html>:根元素,lang属性指定文档语言。
  • <head>:包含文档的元数据,如字符集、视口设置、标题等。
  • <meta charset="UTF-8">:指定字符编码为UTF-8,支持多语言。
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">:确保页面在移动设备上正确缩放。
  • <title>:定义页面标题,显示在浏览器标签页上。

1.2 常用HTML5标签

HTML5引入了许多新标签,使语义化更加清晰。以下是一些常用标签:

<header>
    <nav>
        <ul>
            <li><a href="#">首页</a></li>
            <li><a href="#">关于</a></li>
        </ul>
    </nav>
</header>

<main>
    <article>
        <h1>文章标题</h1>
        <p>文章内容...</p>
    </article>
    <aside>
        <h2>侧边栏</h2>
        <p>相关链接...</p>
    </aside>
</main>

<footer>
    <p>&copy; 2023 版权所有</p>
</footer>

语义化标签的优势:

  • 提高可访问性,屏幕阅读器能更好地理解页面结构。
  • 有利于SEO,搜索引擎能更准确地抓取内容。
  • 代码更易读和维护。

1.3 表单与输入类型

HTML5增强了表单功能,提供了多种新的输入类型:

<form>
    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email" required>
    
    <label for="date">日期:</label>
    <input type="date" id="date" name="date">
    
    <label for="range">范围:</label>
    <input type="range" id="range" name="range" min="0" max="100">
    
    <label for="color">颜色:</label>
    <input type="color" id="color" name="color">
    
    <label for="file">文件:</label>
    <input type="file" id="file" name="file" multiple>
    
    <button type="submit">提交</button>
</form>

新输入类型的优势:

  • 提供更好的用户体验,如日期选择器、颜色选择器。
  • 移动设备上会自动调用相应的键盘类型。
  • 内置验证功能,如type="email"会自动验证邮箱格式。

第二部分:CSS3样式与布局

2.1 CSS3选择器

CSS3提供了更强大的选择器,使样式控制更加灵活:

/* 基本选择器 */
#header { /* ID选择器 */ }
.nav { /* 类选择器 */ }
div { /* 元素选择器 */ }

/* 属性选择器 */
input[type="text"] { /* 匹配type属性为text的input */ }
a[href^="https"] { /* 匹配以https开头的href */ }

/* 伪类选择器 */
a:hover { /* 鼠标悬停 */ }
input:focus { /* 输入框获得焦点 */ }
li:nth-child(odd) { /* 奇数行的li */ }

/* 伪元素选择器 */
p::first-letter { /* 段落首字母 */ }
div::before { /* 元素前插入内容 */ }

2.2 Flexbox布局

Flexbox是CSS3中用于布局的强大工具,特别适合一维布局:

.container {
    display: flex;
    justify-content: space-between; /* 主轴对齐 */
    align-items: center; /* 交叉轴对齐 */
    flex-wrap: wrap; /* 允许换行 */
    gap: 10px; /* 项目间距 */
}

.item {
    flex: 1; /* 等分剩余空间 */
    min-width: 200px; /* 最小宽度 */
}

Flexbox实战示例:

<div class="container">
    <div class="item">项目1</div>
    <div class="item">项目2</div>
    <div class="item">项目3</div>
</div>

2.3 Grid布局

Grid布局是CSS3中用于二维布局的系统:

.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr); /* 三列等宽 */
    grid-template-rows: 100px 200px; /* 两行固定高度 */
    gap: 20px; /* 网格间距 */
}

.grid-item {
    grid-column: 1 / 3; /* 跨越1到3列 */
    grid-row: 1; /* 第一行 */
}

Grid布局示例:

<div class="grid-container">
    <div class="grid-item">头部</div>
    <div class="grid-item">侧边栏</div>
    <div class="grid-item">主内容</div>
    <div class="grid-item">底部</div>
</div>

2.4 CSS3动画与过渡

CSS3提供了强大的动画和过渡效果:

/* 过渡效果 */
.button {
    background-color: #3498db;
    transition: background-color 0.3s ease;
}

.button:hover {
    background-color: #2980b9;
}

/* 关键帧动画 */
@keyframes slideIn {
    0% {
        transform: translateX(-100%);
        opacity: 0;
    }
    100% {
        transform: translateX(0);
        opacity: 1;
    }
}

.animated-element {
    animation: slideIn 0.5s ease-out;
}

第三部分:JavaScript核心编程

3.1 ES6+新特性

现代JavaScript开发需要掌握ES6及以上版本的新特性:

// 箭头函数
const add = (a, b) => a + b;

// 解构赋值
const person = { name: 'Alice', age: 30 };
const { name, age } = person;

// 模板字符串
const greeting = `Hello, ${name}! You are ${age} years old.`;

// 默认参数
function greet(name = 'Guest') {
    return `Hello, ${name}!`;
}

// 扩展运算符
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

// Promise
const fetchData = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('数据加载完成');
        }, 1000);
    });
};

// Async/Await
async function loadData() {
    try {
        const data = await fetchData();
        console.log(data);
    } catch (error) {
        console.error('错误:', error);
    }
}

3.2 DOM操作与事件处理

DOM操作是前端开发的核心技能:

// 获取元素
const header = document.getElementById('header');
const items = document.querySelectorAll('.item');

// 创建和插入元素
const newDiv = document.createElement('div');
newDiv.textContent = '新元素';
document.body.appendChild(newDiv);

// 事件处理
const button = document.querySelector('#myButton');
button.addEventListener('click', function(event) {
    console.log('按钮被点击', event.target);
});

// 事件委托
document.querySelector('.list').addEventListener('click', function(e) {
    if (e.target.tagName === 'LI') {
        console.log('点击了列表项:', e.target.textContent);
    }
});

3.3 异步编程与AJAX

现代Web应用离不开异步数据请求:

// 使用fetch API
async function fetchUserData() {
    try {
        const response = await fetch('https://api.example.com/users');
        if (!response.ok) {
            throw new Error('网络响应错误');
        }
        const data = await response.json();
        console.log(data);
        return data;
    } catch (error) {
        console.error('获取数据失败:', error);
    }
}

// 使用axios(需要引入axios库)
// axios.get('https://api.example.com/users')
//     .then(response => console.log(response.data))
//     .catch(error => console.error(error));

第四部分:HTML5高级API

4.1 Canvas绘图

Canvas API允许在网页上绘制图形:

<canvas id="myCanvas" width="400" height="300" style="border:1px solid #000;"></canvas>

<script>
    const canvas = document.getElementById('myCanvas');
    const ctx = canvas.getContext('2d');
    
    // 绘制矩形
    ctx.fillStyle = 'blue';
    ctx.fillRect(10, 10, 150, 100);
    
    // 绘制圆形
    ctx.beginPath();
    ctx.arc(200, 150, 50, 0, Math.PI * 2);
    ctx.fillStyle = 'red';
    ctx.fill();
    
    // 绘制文字
    ctx.font = '20px Arial';
    ctx.fillStyle = 'black';
    ctx.fillText('Hello Canvas!', 100, 250);
</script>

4.2 Web Storage

HTML5提供了本地存储解决方案:

// localStorage
localStorage.setItem('username', 'Alice');
const username = localStorage.getItem('username');
console.log(username); // 输出: Alice

// sessionStorage
sessionStorage.setItem('sessionData', '临时数据');
const sessionData = sessionStorage.getItem('sessionData');

// 存储对象
const user = { name: 'Bob', age: 25 };
localStorage.setItem('user', JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 输出: Bob

// 清除存储
localStorage.removeItem('username');
localStorage.clear();

4.3 Geolocation API

获取用户地理位置:

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
        (position) => {
            const latitude = position.coords.latitude;
            const longitude = position.coords.longitude;
            console.log(`纬度: ${latitude}, 经度: ${longitude}`);
        },
        (error) => {
            console.error('获取位置失败:', error.message);
        },
        {
            enableHighAccuracy: true, // 高精度
            timeout: 5000, // 超时时间
            maximumAge: 0 // 不使用缓存
        }
    );
} else {
    console.log('浏览器不支持地理定位');
}

4.4 Web Workers

Web Workers允许在后台线程中运行JavaScript:

// 主线程代码
const worker = new Worker('worker.js');

worker.postMessage('开始计算');

worker.onmessage = function(event) {
    console.log('从Worker接收到:', event.data);
};

// worker.js 文件内容
self.onmessage = function(event) {
    // 执行耗时计算
    const result = heavyComputation(event.data);
    self.postMessage(result);
};

function heavyComputation(data) {
    // 模拟耗时计算
    let sum = 0;
    for (let i = 0; i < 1000000000; i++) {
        sum += i;
    }
    return sum;
}

第五部分:响应式设计与移动端适配

5.1 媒体查询

媒体查询是响应式设计的核心:

/* 移动设备优先 */
body {
    font-size: 14px;
    line-height: 1.5;
}

/* 平板设备 */
@media (min-width: 768px) {
    body {
        font-size: 16px;
    }
    
    .container {
        max-width: 720px;
        margin: 0 auto;
    }
}

/* 桌面设备 */
@media (min-width: 1024px) {
    body {
        font-size: 18px;
    }
    
    .container {
        max-width: 960px;
    }
    
    .sidebar {
        display: block;
    }
}

5.2 视口单位与弹性布局

使用视口单位实现真正的响应式:

/* 视口单位 */
.container {
    width: 100vw; /* 视口宽度的100% */
    height: 100vh; /* 视口高度的100% */
    padding: 2vw; /* 相对于视口宽度的内边距 */
}

/* 弹性字体 */
html {
    font-size: 16px;
}

@media (min-width: 768px) {
    html {
        font-size: 18px;
    }
}

/* 使用clamp()函数 */
h1 {
    font-size: clamp(1.5rem, 5vw, 3rem); /* 最小值, 优先值, 最大值 */
}

5.3 移动端触摸事件

处理移动端的触摸交互:

// 触摸事件
const element = document.getElementById('touchArea');

element.addEventListener('touchstart', function(event) {
    console.log('触摸开始', event.touches[0].clientX);
});

element.addEventListener('touchmove', function(event) {
    console.log('触摸移动', event.touches[0].clientX);
});

element.addEventListener('touchend', function(event) {
    console.log('触摸结束');
});

// 防止页面滚动
element.addEventListener('touchmove', function(event) {
    event.preventDefault(); // 阻止默认行为
}, { passive: false });

第六部分:前端工程化与工具链

6.1 模块化开发

现代前端开发需要模块化:

// ES6模块导出
// utils.js
export const formatDate = (date) => {
    return new Date(date).toLocaleDateString();
};

export default class Calculator {
    add(a, b) {
        return a + b;
    }
}

// main.js
import Calculator, { formatDate } from './utils.js';

const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
console.log(formatDate(new Date())); // 格式化日期

6.2 构建工具

使用Webpack进行项目构建:

// webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    },
    devServer: {
        static: {
            directory: path.join(__dirname, 'public'),
        },
        compress: true,
        port: 9000,
    }
};

6.3 版本控制

使用Git进行版本管理:

# 初始化仓库
git init

# 添加文件
git add .

# 提交更改
git commit -m "Initial commit"

# 创建分支
git branch feature/new-feature

# 切换分支
git checkout feature/new-feature

# 合并分支
git checkout main
git merge feature/new-feature

# 推送到远程仓库
git remote add origin https://github.com/username/repo.git
git push -u origin main

第七部分:实战项目开发

7.1 项目一:个人博客系统

项目目标: 创建一个静态个人博客,展示文章列表和详情页。

技术栈: HTML5 + CSS3 + JavaScript

核心功能:

  1. 响应式布局的博客首页
  2. 文章分类和标签系统
  3. 文章详情页
  4. 评论功能(前端模拟)

代码示例:

<!-- 博客首页结构 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的博客</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <header class="blog-header">
        <h1>我的技术博客</h1>
        <nav>
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">分类</a></li>
                <li><a href="#">关于</a></li>
            </ul>
        </nav>
    </header>
    
    <main class="blog-main">
        <section class="posts-list">
            <article class="post-card">
                <h2><a href="post1.html">HTML5新特性详解</a></h2>
                <p class="post-meta">2023-10-01 | 前端开发</p>
                <p>HTML5带来了许多新特性,包括语义化标签、新的表单输入类型、Canvas绘图等...</p>
                <a href="post1.html" class="read-more">阅读更多</a>
            </article>
            
            <article class="post-card">
                <h2><a href="post2.html">CSS3动画实战</a></h2>
                <p class="post-meta">2023-09-28 | CSS</p>
                <p>CSS3动画让网页更加生动有趣,通过关键帧动画可以实现复杂的视觉效果...</p>
                <a href="post2.html" class="read-more">阅读更多</a>
            </article>
        </section>
        
        <aside class="sidebar">
            <div class="categories">
                <h3>分类</h3>
                <ul>
                    <li><a href="#">前端开发 (5)</a></li>
                    <li><a href="#">JavaScript (3)</a></li>
                    <li><a href="#">CSS (2)</a></li>
                </ul>
            </div>
            
            <div class="tags">
                <h3>标签云</h3>
                <div class="tag-cloud">
                    <a href="#" style="font-size: 1.2em">HTML5</a>
                    <a href="#" style="font-size: 1em">CSS3</a>
                    <a href="#" style="font-size: 1.5em">JavaScript</a>
                    <a href="#" style="font-size: 0.9em">响应式</a>
                </div>
            </div>
        </aside>
    </main>
    
    <footer class="blog-footer">
        <p>&copy; 2023 我的博客 | 由HTML5构建</p>
    </footer>
    
    <script src="script.js"></script>
</body>
</html>

CSS样式示例:

/* styles.css */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f5f5f5;
}

.blog-header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 2rem 0;
    text-align: center;
}

.blog-header h1 {
    font-size: 2.5rem;
    margin-bottom: 1rem;
}

.blog-header nav ul {
    list-style: none;
    display: flex;
    justify-content: center;
    gap: 2rem;
}

.blog-header nav a {
    color: white;
    text-decoration: none;
    font-weight: 500;
    transition: opacity 0.3s;
}

.blog-header nav a:hover {
    opacity: 0.8;
}

.blog-main {
    max-width: 1200px;
    margin: 2rem auto;
    padding: 0 1rem;
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 2rem;
}

.posts-list {
    display: flex;
    flex-direction: column;
    gap: 2rem;
}

.post-card {
    background: white;
    padding: 1.5rem;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    transition: transform 0.3s, box-shadow 0.3s;
}

.post-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}

.post-card h2 a {
    color: #333;
    text-decoration: none;
    font-size: 1.5rem;
    transition: color 0.3s;
}

.post-card h2 a:hover {
    color: #667eea;
}

.post-meta {
    color: #666;
    font-size: 0.9rem;
    margin: 0.5rem 0;
}

.read-more {
    display: inline-block;
    margin-top: 1rem;
    color: #667eea;
    text-decoration: none;
    font-weight: 600;
    transition: color 0.3s;
}

.read-more:hover {
    color: #764ba2;
}

.sidebar {
    display: flex;
    flex-direction: column;
    gap: 2rem;
}

.sidebar > div {
    background: white;
    padding: 1.5rem;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.sidebar h3 {
    margin-bottom: 1rem;
    color: #333;
    border-bottom: 2px solid #667eea;
    padding-bottom: 0.5rem;
}

.sidebar ul {
    list-style: none;
}

.sidebar ul li {
    margin-bottom: 0.5rem;
}

.sidebar a {
    color: #555;
    text-decoration: none;
    transition: color 0.3s;
}

.sidebar a:hover {
    color: #667eea;
}

.tag-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.tag-cloud a {
    background: #f0f0f0;
    padding: 0.3rem 0.8rem;
    border-radius: 20px;
    color: #555;
    text-decoration: none;
    transition: all 0.3s;
}

.tag-cloud a:hover {
    background: #667eea;
    color: white;
}

.blog-footer {
    background: #333;
    color: white;
    text-align: center;
    padding: 1.5rem 0;
    margin-top: 2rem;
}

/* 响应式设计 */
@media (max-width: 768px) {
    .blog-main {
        grid-template-columns: 1fr;
    }
    
    .blog-header h1 {
        font-size: 2rem;
    }
    
    .blog-header nav ul {
        flex-direction: column;
        gap: 0.5rem;
    }
    
    .post-card {
        padding: 1rem;
    }
}

JavaScript交互示例:

// script.js
document.addEventListener('DOMContentLoaded', function() {
    // 1. 文章卡片点击效果
    const postCards = document.querySelectorAll('.post-card');
    postCards.forEach(card => {
        card.addEventListener('click', function(e) {
            // 如果点击的是链接,不阻止默认行为
            if (e.target.tagName === 'A') return;
            
            // 添加点击动画
            this.style.transform = 'scale(0.98)';
            setTimeout(() => {
                this.style.transform = '';
            }, 150);
            
            // 跳转到文章详情页
            const link = this.querySelector('h2 a');
            if (link) {
                window.location.href = link.href;
            }
        });
    });
    
    // 2. 标签云点击统计
    const tagLinks = document.querySelectorAll('.tag-cloud a');
    tagLinks.forEach(tag => {
        tag.addEventListener('click', function(e) {
            e.preventDefault();
            
            // 显示点击提示
            const toast = document.createElement('div');
            toast.textContent = `已选择标签: ${this.textContent}`;
            toast.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: #667eea;
                color: white;
                padding: 10px 20px;
                border-radius: 5px;
                z-index: 1000;
                animation: slideIn 0.3s ease-out;
            `;
            document.body.appendChild(toast);
            
            // 3秒后移除
            setTimeout(() => {
                toast.remove();
            }, 3000);
            
            // 这里可以添加实际的筛选逻辑
            console.log(`筛选标签: ${this.textContent}`);
        });
    });
    
    // 3. 滚动到顶部按钮
    const backToTop = document.createElement('button');
    backToTop.textContent = '↑';
    backToTop.style.cssText = `
        position: fixed;
        bottom: 30px;
        right: 30px;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background: #667eea;
        color: white;
        border: none;
        font-size: 1.5rem;
        cursor: pointer;
        opacity: 0;
        transition: opacity 0.3s, transform 0.3s;
        z-index: 100;
    `;
    document.body.appendChild(backToTop);
    
    // 监听滚动事件
    window.addEventListener('scroll', function() {
        if (window.pageYOffset > 300) {
            backToTop.style.opacity = '1';
            backToTop.style.transform = 'scale(1)';
        } else {
            backToTop.style.opacity = '0';
            backToTop.style.transform = 'scale(0.8)';
        }
    });
    
    // 点击回到顶部
    backToTop.addEventListener('click', function() {
        window.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
    });
    
    // 4. 模拟评论功能
    const commentForm = document.createElement('div');
    commentForm.innerHTML = `
        <div style="background: white; padding: 1.5rem; border-radius: 8px; margin-top: 2rem; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
            <h3 style="margin-bottom: 1rem; color: #333;">发表评论</h3>
            <textarea id="commentText" placeholder="写下你的想法..." style="width: 100%; padding: 0.8rem; border: 1px solid #ddd; border-radius: 4px; resize: vertical; min-height: 80px;"></textarea>
            <button id="submitComment" style="margin-top: 1rem; padding: 0.5rem 1.5rem; background: #667eea; color: white; border: none; border-radius: 4px; cursor: pointer;">提交评论</button>
            <div id="commentsList" style="margin-top: 1.5rem;"></div>
        </div>
    `;
    document.querySelector('.posts-list').appendChild(commentForm);
    
    // 提交评论
    document.getElementById('submitComment').addEventListener('click', function() {
        const text = document.getElementById('commentText').value.trim();
        if (!text) {
            alert('请输入评论内容');
            return;
        }
        
        const comment = document.createElement('div');
        comment.style.cssText = `
            background: #f9f9f9;
            padding: 1rem;
            margin-bottom: 0.5rem;
            border-radius: 4px;
            border-left: 3px solid #667eea;
            animation: slideIn 0.3s ease-out;
        `;
        
        const date = new Date().toLocaleString();
        comment.innerHTML = `
            <p style="margin-bottom: 0.5rem;">${text}</p>
            <small style="color: #666;">匿名用户 | ${date}</small>
        `;
        
        document.getElementById('commentsList').prepend(comment);
        document.getElementById('commentText').value = '';
        
        // 保存到localStorage
        const comments = JSON.parse(localStorage.getItem('blogComments') || '[]');
        comments.unshift({
            text: text,
            date: date,
            user: '匿名用户'
        });
        localStorage.setItem('blogComments', JSON.stringify(comments.slice(0, 10))); // 只保留最近10条
    });
    
    // 页面加载时恢复评论
    const savedComments = JSON.parse(localStorage.getItem('blogComments') || '[]');
    savedComments.forEach(commentData => {
        const comment = document.createElement('div');
        comment.style.cssText = `
            background: #f9f9f9;
            padding: 1rem;
            margin-bottom: 0.5rem;
            border-radius: 4px;
            border-left: 3px solid #667eea;
        `;
        comment.innerHTML = `
            <p style="margin-bottom: 0.5rem;">${commentData.text}</p>
            <small style="color: #666;">${commentData.user} | ${commentData.date}</small>
        `;
        document.getElementById('commentsList').appendChild(comment);
    });
});

7.2 项目二:待办事项应用(Todo List)

项目目标: 创建一个功能完整的待办事项管理应用。

技术栈: HTML5 + CSS3 + JavaScript (ES6+)

核心功能:

  1. 添加、删除、标记完成任务
  2. 任务分类和筛选
  3. 数据持久化(localStorage)
  4. 拖拽排序(可选)

完整代码示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>待办事项管理器</title>
    <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%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .todo-container {
            background: white;
            width: 100%;
            max-width: 500px;
            border-radius: 12px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            overflow: hidden;
        }

        .todo-header {
            background: #667eea;
            color: white;
            padding: 1.5rem;
            text-align: center;
        }

        .todo-header h1 {
            font-size: 1.8rem;
            margin-bottom: 0.5rem;
        }

        .todo-header p {
            opacity: 0.9;
            font-size: 0.9rem;
        }

        .input-section {
            padding: 1.5rem;
            border-bottom: 1px solid #eee;
        }

        .input-group {
            display: flex;
            gap: 0.5rem;
        }

        .input-group input {
            flex: 1;
            padding: 0.8rem;
            border: 2px solid #ddd;
            border-radius: 6px;
            font-size: 1rem;
            transition: border-color 0.3s;
        }

        .input-group input:focus {
            outline: none;
            border-color: #667eea;
        }

        .input-group select {
            padding: 0.8rem;
            border: 2px solid #ddd;
            border-radius: 6px;
            background: white;
            cursor: pointer;
        }

        .input-group button {
            padding: 0.8rem 1.5rem;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            transition: background 0.3s;
        }

        .input-group button:hover {
            background: #5a6fd8;
        }

        .filter-section {
            padding: 1rem 1.5rem;
            background: #f9f9f9;
            display: flex;
            gap: 0.5rem;
            flex-wrap: wrap;
        }

        .filter-btn {
            padding: 0.5rem 1rem;
            border: 1px solid #ddd;
            background: white;
            border-radius: 20px;
            cursor: pointer;
            transition: all 0.3s;
            font-size: 0.9rem;
        }

        .filter-btn.active {
            background: #667eea;
            color: white;
            border-color: #667eea;
        }

        .filter-btn:hover {
            border-color: #667eea;
        }

        .stats {
            padding: 0.5rem 1.5rem;
            background: #f0f0f0;
            font-size: 0.85rem;
            color: #666;
            display: flex;
            justify-content: space-between;
        }

        .todo-list {
            padding: 1rem;
            max-height: 400px;
            overflow-y: auto;
        }

        .todo-item {
            display: flex;
            align-items: center;
            padding: 1rem;
            background: white;
            border-radius: 8px;
            margin-bottom: 0.5rem;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            transition: all 0.3s;
            cursor: grab;
        }

        .todo-item:hover {
            transform: translateX(5px);
            box-shadow: 0 4px 10px rgba(0,0,0,0.1);
        }

        .todo-item.dragging {
            opacity: 0.5;
            transform: scale(0.98);
        }

        .todo-item.completed {
            opacity: 0.6;
            background: #f9f9f9;
        }

        .todo-item.completed .todo-text {
            text-decoration: line-through;
            color: #999;
        }

        .todo-checkbox {
            width: 20px;
            height: 20px;
            margin-right: 1rem;
            cursor: pointer;
        }

        .todo-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 0.2rem;
        }

        .todo-text {
            font-size: 1rem;
            color: #333;
        }

        .todo-meta {
            font-size: 0.75rem;
            color: #888;
            display: flex;
            gap: 0.5rem;
        }

        .todo-category {
            background: #e0e0e0;
            padding: 0.1rem 0.5rem;
            border-radius: 10px;
            font-size: 0.7rem;
        }

        .todo-actions {
            display: flex;
            gap: 0.5rem;
        }

        .todo-actions button {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 1.1rem;
            padding: 0.3rem;
            border-radius: 4px;
            transition: background 0.3s;
        }

        .todo-actions button:hover {
            background: #f0f0f0;
        }

        .delete-btn:hover {
            color: #e74c3c;
            background: #fee;
        }

        .empty-state {
            text-align: center;
            padding: 3rem 1rem;
            color: #999;
        }

        .empty-state p {
            margin-top: 0.5rem;
            font-size: 0.9rem;
        }

        /* 动画 */
        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateY(-20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .todo-item {
            animation: slideIn 0.3s ease-out;
        }

        /* 响应式设计 */
        @media (max-width: 480px) {
            .input-group {
                flex-direction: column;
            }
            
            .input-group select {
                width: 100%;
            }
            
            .filter-section {
                justify-content: center;
            }
            
            .todo-header h1 {
                font-size: 1.5rem;
            }
        }
    </style>
</head>
<body>
    <div class="todo-container">
        <div class="todo-header">
            <h1>待办事项管理器</h1>
            <p>高效管理你的任务</p>
        </div>
        
        <div class="input-section">
            <div class="input-group">
                <input type="text" id="taskInput" placeholder="输入新任务..." maxlength="100">
                <select id="categorySelect">
                    <option value="work">工作</option>
                    <option value="personal">个人</option>
                    <option value="shopping">购物</option>
                    <option value="other">其他</option>
                </select>
                <button id="addBtn">添加</button>
            </div>
        </div>
        
        <div class="filter-section">
            <button class="filter-btn active" data-filter="all">全部</button>
            <button class="filter-btn" data-filter="active">未完成</button>
            <button class="filter-btn" data-filter="completed">已完成</button>
            <button class="filter-btn" data-filter="work">工作</button>
            <button class="filter-btn" data-filter="personal">个人</button>
        </div>
        
        <div class="stats">
            <span id="totalTasks">总任务: 0</span>
            <span id="completedTasks">已完成: 0</span>
        </div>
        
        <div class="todo-list" id="todoList">
            <div class="empty-state">
                <p>暂无任务</p>
                <p>添加第一个任务开始吧!</p>
            </div>
        </div>
    </div>

    <script>
        class TodoManager {
            constructor() {
                this.tasks = this.loadTasks();
                this.currentFilter = 'all';
                this.init();
            }

            init() {
                this.bindEvents();
                this.render();
            }

            bindEvents() {
                // 添加任务
                document.getElementById('addBtn').addEventListener('click', () => this.addTask());
                document.getElementById('taskInput').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') this.addTask();
                });

                // 筛选按钮
                document.querySelectorAll('.filter-btn').forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
                        e.target.classList.add('active');
                        this.currentFilter = e.target.dataset.filter;
                        this.render();
                    });
                });

                // 拖拽功能
                this.initDragAndDrop();
            }

            addTask() {
                const input = document.getElementById('taskInput');
                const category = document.getElementById('categorySelect').value;
                const text = input.value.trim();

                if (!text) {
                    this.showNotification('请输入任务内容');
                    return;
                }

                const task = {
                    id: Date.now(),
                    text: text,
                    category: category,
                    completed: false,
                    createdAt: new Date().toISOString()
                };

                this.tasks.unshift(task);
                this.saveTasks();
                this.render();
                input.value = '';
                this.showNotification('任务添加成功!');
            }

            toggleTask(id) {
                const task = this.tasks.find(t => t.id === id);
                if (task) {
                    task.completed = !task.completed;
                    this.saveTasks();
                    this.render();
                }
            }

            deleteTask(id) {
                if (confirm('确定要删除这个任务吗?')) {
                    this.tasks = this.tasks.filter(t => t.id !== id);
                    this.saveTasks();
                    this.render();
                    this.showNotification('任务已删除');
                }
            }

            getFilteredTasks() {
                let filtered = [...this.tasks];

                // 按状态筛选
                if (this.currentFilter === 'active') {
                    filtered = filtered.filter(t => !t.completed);
                } else if (this.currentFilter === 'completed') {
                    filtered = filtered.filter(t => t.completed);
                } else if (['work', 'personal', 'shopping', 'other'].includes(this.currentFilter)) {
                    filtered = filtered.filter(t => t.category === this.currentFilter);
                }

                return filtered;
            }

            render() {
                const todoList = document.getElementById('todoList');
                const filteredTasks = this.getFilteredTasks();

                // 更新统计
                document.getElementById('totalTasks').textContent = `总任务: ${this.tasks.length}`;
                document.getElementById('completedTasks').textContent = `已完成: ${this.tasks.filter(t => t.completed).length}`;

                if (filteredTasks.length === 0) {
                    todoList.innerHTML = `
                        <div class="empty-state">
                            <p>${this.currentFilter === 'all' ? '暂无任务' : '没有符合条件的任务'}</p>
                            <p>${this.currentFilter === 'all' ? '添加第一个任务开始吧!' : '试试其他筛选条件'}</p>
                        </div>
                    `;
                    return;
                }

                todoList.innerHTML = filteredTasks.map(task => `
                    <div class="todo-item ${task.completed ? 'completed' : ''}" 
                         data-id="${task.id}" 
                         draggable="true">
                        <input type="checkbox" 
                               class="todo-checkbox" 
                               ${task.completed ? 'checked' : ''} 
                               onchange="todoManager.toggleTask(${task.id})">
                        <div class="todo-content">
                            <span class="todo-text">${this.escapeHtml(task.text)}</span>
                            <div class="todo-meta">
                                <span class="todo-category">${this.getCategoryName(task.category)}</span>
                                <span>${this.formatDate(task.createdAt)}</span>
                            </div>
                        </div>
                        <div class="todo-actions">
                            <button class="delete-btn" onclick="todoManager.deleteTask(${task.id})" title="删除">🗑️</button>
                        </div>
                    </div>
                `).join('');

                // 重新绑定拖拽事件
                this.initDragAndDrop();
            }

            initDragAndDrop() {
                const items = document.querySelectorAll('.todo-item');
                let draggedItem = null;

                items.forEach(item => {
                    item.addEventListener('dragstart', (e) => {
                        draggedItem = item;
                        item.classList.add('dragging');
                        e.dataTransfer.effectAllowed = 'move';
                    });

                    item.addEventListener('dragend', () => {
                        item.classList.remove('dragging');
                        draggedItem = null;
                    });

                    item.addEventListener('dragover', (e) => {
                        e.preventDefault();
                        const afterElement = this.getDragAfterElement(e.clientY);
                        if (afterElement == null) {
                            item.parentNode.appendChild(draggedItem);
                        } else {
                            item.parentNode.insertBefore(draggedItem, afterElement);
                        }
                    });

                    item.addEventListener('drop', (e) => {
                        e.preventDefault();
                        this.updateOrder();
                    });
                });
            }

            getDragAfterElement(y) {
                const items = [...document.querySelectorAll('.todo-item:not(.dragging)')];
                return items.reduce((closest, child) => {
                    const box = child.getBoundingClientRect();
                    const offset = y - box.top - box.height / 2;
                    if (offset < 0 && offset > closest.offset) {
                        return { offset: offset, element: child };
                    } else {
                        return closest;
                    }
                }, { offset: Number.NEGATIVE_INFINITY }).element;
            }

            updateOrder() {
                const items = document.querySelectorAll('.todo-item');
                const newOrder = [];
                items.forEach(item => {
                    const id = parseInt(item.dataset.id);
                    const task = this.tasks.find(t => t.id === id);
                    if (task) newOrder.push(task);
                });
                this.tasks = newOrder;
                this.saveTasks();
            }

            saveTasks() {
                localStorage.setItem('todoTasks', JSON.stringify(this.tasks));
            }

            loadTasks() {
                const saved = localStorage.getItem('todoTasks');
                return saved ? JSON.parse(saved) : [];
            }

            getCategoryName(category) {
                const names = {
                    work: '工作',
                    personal: '个人',
                    shopping: '购物',
                    other: '其他'
                };
                return names[category] || category;
            }

            formatDate(dateString) {
                const date = new Date(dateString);
                return date.toLocaleDateString('zh-CN', {
                    month: 'short',
                    day: 'numeric',
                    hour: '2-digit',
                    minute: '2-digit'
                });
            }

            escapeHtml(text) {
                const div = document.createElement('div');
                div.textContent = text;
                return div.innerHTML;
            }

            showNotification(message) {
                const notification = document.createElement('div');
                notification.textContent = message;
                notification.style.cssText = `
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: #4CAF50;
                    color: white;
                    padding: 12px 20px;
                    border-radius: 6px;
                    z-index: 1000;
                    animation: slideIn 0.3s ease-out;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                `;
                document.body.appendChild(notification);

                setTimeout(() => {
                    notification.style.opacity = '0';
                    notification.style.transform = 'translateX(100%)';
                    setTimeout(() => notification.remove(), 300);
                }, 2000);
            }
        }

        // 初始化应用
        const todoManager = new TodoManager();
    </script>
</body>
</html>

7.3 项目三:响应式图片画廊

项目目标: 创建一个支持响应式、懒加载和模态框查看的图片画廊。

技术栈: HTML5 + CSS3 + JavaScript (Intersection Observer API)

核心功能:

  1. 响应式网格布局
  2. 图片懒加载
  3. 点击放大查看
  4. 键盘导航支持

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>响应式图片画廊</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #f5f5f5;
            padding: 20px;
        }

        .gallery-header {
            text-align: center;
            margin-bottom: 2rem;
        }

        .gallery-header h1 {
            color: #333;
            margin-bottom: 0.5rem;
        }

        .gallery-header p {
            color: #666;
            font-size: 1.1rem;
        }

        .gallery-container {
            max-width: 1200px;
            margin: 0 auto;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 20px;
        }

        .gallery-item {
            position: relative;
            aspect-ratio: 4/3;
            border-radius: 12px;
            overflow: hidden;
            cursor: pointer;
            background: #e0e0e0;
            transition: transform 0.3s, box-shadow 0.3s;
        }

        .gallery-item:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 30px rgba(0,0,0,0.15);
        }

        .gallery-item img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            opacity: 0;
            transition: opacity 0.5s;
        }

        .gallery-item img.loaded {
            opacity: 1;
        }

        .gallery-item .placeholder {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(45deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
            background-size: 200% 200%;
            animation: shimmer 1.5s infinite;
        }

        .gallery-item .overlay {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            background: linear-gradient(transparent, rgba(0,0,0,0.7));
            padding: 1rem;
            color: white;
            transform: translateY(100%);
            transition: transform 0.3s;
        }

        .gallery-item:hover .overlay {
            transform: translateY(0);
        }

        .overlay h3 {
            font-size: 1rem;
            margin-bottom: 0.3rem;
        }

        .overlay p {
            font-size: 0.8rem;
            opacity: 0.8;
        }

        /* 模态框 */
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.9);
            z-index: 1000;
            justify-content: center;
            align-items: center;
            opacity: 0;
            transition: opacity 0.3s;
        }

        .modal.active {
            display: flex;
            opacity: 1;
        }

        .modal-content {
            position: relative;
            max-width: 90%;
            max-height: 90%;
            animation: zoomIn 0.3s ease-out;
        }

        .modal-content img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            border-radius: 8px;
        }

        .modal-close {
            position: absolute;
            top: -40px;
            right: 0;
            background: none;
            border: none;
            color: white;
            font-size: 2rem;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .modal-close:hover {
            transform: scale(1.2);
        }

        .modal-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            background: rgba(255,255,255,0.2);
            border: none;
            color: white;
            font-size: 2rem;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            cursor: pointer;
            transition: background 0.3s;
        }

        .modal-nav:hover {
            background: rgba(255,255,255,0.3);
        }

        .modal-prev {
            left: -70px;
        }

        .modal-next {
            right: -70px;
        }

        .modal-info {
            position: absolute;
            bottom: -60px;
            left: 0;
            right: 0;
            text-align: center;
            color: white;
        }

        .modal-info h3 {
            margin-bottom: 0.3rem;
        }

        .modal-info p {
            opacity: 0.8;
            font-size: 0.9rem;
        }

        /* 动画 */
        @keyframes shimmer {
            0% { background-position: 200% 0; }
            100% { background-position: -200% 0; }
        }

        @keyframes zoomIn {
            from {
                transform: scale(0.8);
                opacity: 0;
            }
            to {
                transform: scale(1);
                opacity: 1;
            }
        }

        /* 响应式调整 */
        @media (max-width: 768px) {
            .gallery-container {
                grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
                gap: 10px;
            }

            .modal-nav {
                display: none;
            }

            .modal-close {
                top: 20px;
                right: 20px;
                background: rgba(0,0,0,0.5);
                width: 40px;
                height: 40px;
                border-radius: 50%;
            }

            .modal-info {
                bottom: 10px;
                background: rgba(0,0,0,0.5);
                padding: 10px;
                border-radius: 8px;
            }
        }

        /* 加载更多按钮 */
        .load-more {
            display: block;
            margin: 2rem auto;
            padding: 1rem 2rem;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 1rem;
            cursor: pointer;
            transition: background 0.3s;
        }

        .load-more:hover {
            background: #5a6fd8;
        }

        .load-more:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
    </style>
</head>
<body>
    <div class="gallery-header">
        <h1>响应式图片画廊</h1>
        <p>点击图片查看大图,支持键盘导航</p>
    </div>

    <div class="gallery-container" id="gallery">
        <!-- 图片将通过JavaScript动态加载 -->
    </div>

    <button class="load-more" id="loadMore">加载更多</button>

    <!-- 模态框 -->
    <div class="modal" id="modal">
        <div class="modal-content">
            <button class="modal-close" id="modalClose">×</button>
            <button class="modal-nav modal-prev" id="modalPrev">‹</button>
            <button class="modal-nav modal-next" id="modalNext">›</button>
            <img id="modalImage" src="" alt="">
            <div class="modal-info">
                <h3 id="modalTitle"></h3>
                <p id="modalDesc"></p>
            </div>
        </div>
    </div>

    <script>
        class GalleryManager {
            constructor() {
                this.images = [];
                this.currentIndex = 0;
                this.page = 1;
                this.isLoading = false;
                this.observer = null;
                this.init();
            }

            init() {
                this.bindEvents();
                this.initIntersectionObserver();
                this.loadImages();
            }

            bindEvents() {
                // 加载更多
                document.getElementById('loadMore').addEventListener('click', () => {
                    this.loadImages();
                });

                // 模态框控制
                document.getElementById('modalClose').addEventListener('click', () => this.closeModal());
                document.getElementById('modalPrev').addEventListener('click', () => this.navigateModal(-1));
                document.getElementById('modalNext').addEventListener('click', () => this.navigateModal(1));

                // 点击模态框背景关闭
                document.getElementById('modal').addEventListener('click', (e) => {
                    if (e.target.id === 'modal') this.closeModal();
                });

                // 键盘导航
                document.addEventListener('keydown', (e) => {
                    if (!document.getElementById('modal').classList.contains('active')) return;
                    
                    if (e.key === 'Escape') this.closeModal();
                    if (e.key === 'ArrowLeft') this.navigateModal(-1);
                    if (e.key === 'ArrowRight') this.navigateModal(1);
                });
            }

            initIntersectionObserver() {
                this.observer = new IntersectionObserver((entries) => {
                    entries.forEach(entry => {
                        if (entry.isIntersecting) {
                            const img = entry.target;
                            const src = img.dataset.src;
                            if (src) {
                                img.src = src;
                                img.onload = () => {
                                    img.classList.add('loaded');
                                    const placeholder = img.previousElementSibling;
                                    if (placeholder) placeholder.remove();
                                };
                                this.observer.unobserve(img);
                            }
                        }
                    });
                }, {
                    rootMargin: '50px',
                    threshold: 0.1
                });
            }

            async loadImages() {
                if (this.isLoading) return;
                
                this.isLoading = true;
                const loadMoreBtn = document.getElementById('loadMore');
                loadMoreBtn.textContent = '加载中...';
                loadMoreBtn.disabled = true;

                try {
                    // 模拟API请求
                    const newImages = await this.fetchImages(this.page);
                    this.images = [...this.images, ...newImages];
                    this.renderImages(newImages);
                    this.page++;

                    // 如果加载了所有图片,隐藏加载按钮
                    if (this.page > 5) {
                        loadMoreBtn.style.display = 'none';
                    }
                } catch (error) {
                    console.error('加载图片失败:', error);
                    this.showNotification('加载失败,请重试');
                } finally {
                    this.isLoading = false;
                    loadMoreBtn.textContent = '加载更多';
                    loadMoreBtn.disabled = false;
                }
            }

            async fetchImages(page) {
                // 模拟API延迟
                await new Promise(resolve => setTimeout(resolve, 800));

                // 生成模拟数据
                const categories = ['自然', '城市', '人物', '抽象', '动物'];
                const images = [];
                
                for (let i = 0; i < 12; i++) {
                    const id = (page - 1) * 12 + i + 1;
                    const width = 400 + Math.floor(Math.random() * 200);
                    const height = 300 + Math.floor(Math.random() * 150);
                    const category = categories[Math.floor(Math.random() * categories.length)];
                    
                    images.push({
                        id: id,
                        title: `图片 ${id}`,
                        description: `${category} - 第${page}页`,
                        category: category,
                        // 使用picsum.photos作为示例图片源
                        thumbnail: `https://picsum.photos/${width}/${height}?random=${id}`,
                        full: `https://picsum.photos/1200/800?random=${id}`
                    });
                }

                return images;
            }

            renderImages(newImages) {
                const gallery = document.getElementById('gallery');
                
                newImages.forEach((image, index) => {
                    const item = document.createElement('div');
                    item.className = 'gallery-item';
                    item.dataset.index = this.images.length - newImages.length + index;
                    
                    item.innerHTML = `
                        <div class="placeholder"></div>
                        <img data-src="${image.thumbnail}" alt="${image.title}">
                        <div class="overlay">
                            <h3>${image.title}</h3>
                            <p>${image.description}</p>
                        </div>
                    `;
                    
                    gallery.appendChild(item);
                    
                    // 观察图片实现懒加载
                    const img = item.querySelector('img');
                    this.observer.observe(img);
                    
                    // 点击事件
                    item.addEventListener('click', () => {
                        this.openModal(parseInt(item.dataset.index));
                    });
                });
            }

            openModal(index) {
                this.currentIndex = index;
                const image = this.images[index];
                
                const modal = document.getElementById('modal');
                const modalImage = document.getElementById('modalImage');
                const modalTitle = document.getElementById('modalTitle');
                const modalDesc = document.getElementById('modalDesc');
                
                modalImage.src = image.full;
                modalImage.alt = image.title;
                modalTitle.textContent = image.title;
                modalDesc.textContent = image.description;
                
                modal.classList.add('active');
                document.body.style.overflow = 'hidden';
            }

            closeModal() {
                const modal = document.getElementById('modal');
                modal.classList.remove('active');
                document.body.style.overflow = '';
            }

            navigateModal(direction) {
                const newIndex = this.currentIndex + direction;
                
                if (newIndex >= 0 && newIndex < this.images.length) {
                    this.currentIndex = newIndex;
                    const image = this.images[newIndex];
                    
                    const modalImage = document.getElementById('modalImage');
                    const modalTitle = document.getElementById('modalTitle');
                    const modalDesc = document.getElementById('modalDesc');
                    
                    // 添加淡入淡出效果
                    modalImage.style.opacity = '0';
                    setTimeout(() => {
                        modalImage.src = image.full;
                        modalImage.alt = image.title;
                        modalTitle.textContent = image.title;
                        modalDesc.textContent = image.description;
                        modalImage.style.opacity = '1';
                    }, 150);
                }
            }

            showNotification(message) {
                const notification = document.createElement('div');
                notification.textContent = message;
                notification.style.cssText = `
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    background: #e74c3c;
                    color: white;
                    padding: 12px 20px;
                    border-radius: 6px;
                    z-index: 1001;
                    animation: slideIn 0.3s ease-out;
                `;
                document.body.appendChild(notification);

                setTimeout(() => notification.remove(), 3000);
            }
        }

        // 初始化画廊
        const galleryManager = new GalleryManager();
    </script>
</body>
</html>

第八部分:学习资源与进阶路径

8.1 推荐学习资源

在线教程:

  • MDN Web Docs(最权威的Web技术文档)
  • freeCodeCamp(免费的交互式编程课程)
  • W3Schools(基础语法参考)

视频课程:

  • Udemy上的”Complete Web Development Bootcamp”
  • Coursera的”Web Design for Everybody”
  • YouTube频道:Traversy Media, The Net Ninja

书籍推荐:

  • 《HTML5与CSS3权威指南》
  • 《JavaScript高级程序设计》
  • 《深入浅出React和Redux》

8.2 进阶学习路径

  1. 框架学习:

    • React/Vue/Angular 选择一个深入学习
    • 状态管理(Redux/Vuex)
    • 路由管理(React Router/Vue Router)
  2. 性能优化:

    • 代码分割与懒加载
    • 图片优化与WebP格式
    • 缓存策略与Service Worker
  3. TypeScript:

    • 类型系统基础
    • 接口与泛型
    • 在React/Vue中使用TypeScript
  4. 测试:

    • 单元测试(Jest/Vitest)
    • 端到端测试(Cypress/Playwright)
    • 测试驱动开发(TDD)

8.3 实战项目建议

  1. 初级项目:

    • 个人简历页面
    • 产品展示网站
    • 简单的计算器
  2. 中级项目:

    • 电商网站前端
    • 社交媒体应用
    • 实时聊天应用
  3. 高级项目:

    • 在线编辑器(如Markdown编辑器)
    • 数据可视化仪表盘
    • 渐进式Web应用(PWA)

结语

掌握HTML5前端开发核心技能需要系统的学习和大量的实践。从基础语法到高级API,从CSS布局到JavaScript编程,每一步都需要扎实掌握。通过实战项目,你可以将理论知识转化为实际能力,解决真实世界的问题。

记住,前端开发是一个快速发展的领域,持续学习和实践是保持竞争力的关键。希望这份全攻略能为你的学习之路提供清晰的指引,祝你在前端开发的道路上取得成功!