🚀 第十八章:综合实战项目

通过实战项目巩固所学知识

项目 A:交互式待办事项清单 (Todo List)

涵盖:DOM 操作、事件委托、LocalStorage 持久化、CRUD 逻辑。

功能需求

// 基本结构
// index.html
// <!DOCTYPE html>
// <html lang="zh-CN">
// <head>
//     <meta charset="UTF-8">
//     <meta name="viewport" content="width=device-width, initial-scale=1.0">
//     <title>Todo List</title>
//     <style>
//         /* 样式 */
//     </style>
// </head>
// <body>
//     <div class="container">
//         <h1>Todo List</h1>
//         <form id="todo-form">
//             <input type="text" id="todo-input" placeholder="添加新任务...">
//             <button type="submit">添加</button>
//         </form>
//         <ul id="todo-list"></ul>
//
// // // // // app.js class TodoList { constructor() { this.todos = JSON.parse(localStorage.getItem('todos')) || []; this.init(); } init() { this.render(); this.bindEvents(); } bindEvents() { const form = document.getElementById('todo-form'); form.addEventListener('submit', (e) => { e.preventDefault(); this.addTodo(); }); } addTodo() { const input = document.getElementById('todo-input'); const text = input.value.trim(); if (text) { const todo = { id: Date.now(), text, completed: false }; this.todos.push(todo); this.save(); this.render(); input.value = ''; } } toggleTodo(id) { this.todos = this.todos.map(todo => { if (todo.id === id) { return { ...todo, completed: !todo.completed }; } return todo; }); this.save(); this.render(); } deleteTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); this.save(); this.render(); } save() { localStorage.setItem('todos', JSON.stringify(this.todos)); } render() { const list = document.getElementById('todo-list'); list.innerHTML = ''; this.todos.forEach(todo => { const li = document.createElement('li'); li.innerHTML = ` ${todo.text} `; list.appendChild(li); }); // 事件委托 list.addEventListener('change', (e) => { if (e.target.type === 'checkbox') { const id = parseInt(e.target.dataset.id); this.toggleTodo(id); } }); list.addEventListener('click', (e) => { if (e.target.tagName === 'BUTTON') { const id = parseInt(e.target.dataset.id); this.deleteTodo(id); } }); } } new TodoList();

项目 B:实时天气仪表盘

涵盖:Fetch API、Async/Await、错误处理、动态图表渲染、地理定位。

功能需求

// app.js
class WeatherApp {
    constructor() {
        this.apiKey = 'YOUR_API_KEY'; // 替换为真实的 API key
        this.init();
    }
    
    init() {
        this.getLocation();
    }
    
    getLocation() {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                async (position) => {
                    const { latitude, longitude } = position.coords;
                    await this.getWeather(latitude, longitude);
                },
                (error) => {
                    console.error(error);
                    this.showError('无法获取位置信息');
                }
            );
        } else {
            this.showError('浏览器不支持地理定位');
        }
    }
    
    async getWeather(lat, lon) {
        try {
            this.showLoading();
            const response = await fetch(
                `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${this.apiKey}&units=metric&lang=zh_cn`
            );
            
            if (!response.ok) {
                throw new Error('获取天气失败');
            }
            
            const data = await response.json;
            this.renderWeather(data);
        } catch (error) {
            console.error(error);
            this.showError('获取天气失败,请重试');
        } finally {
            this.hideLoading();
        }
    }
    
    renderWeather(data) {
        const weatherContainer = document.getElementById('weather-container');
        weatherContainer.innerHTML = `
            <h2>${data.name}</h2>
            <div class="weather-info">
                <div class="temp">${Math.round(data.main.temp)}°C
${data.weather[0].description}
湿度: ${data.main.humidity}%
风力: ${data.wind.speed} m/s
气压: ${data.main.pressure} hPa
`; // 这里可以添加图表渲染逻辑 } showLoading() { const loading = document.getElementById('loading'); loading.style.display = 'block'; } hideLoading() { const loading = document.getElementById('loading'); loading.style.display = 'none'; } showError(message) { const error = document.getElementById('error'); error.textContent = message; error.style.display = 'block'; } } new WeatherApp();

项目 C:无限滚动图片流

涵盖:Intersection Observer、虚拟列表、图片懒加载、并发请求控制。

功能需求

// app.js
class InfiniteGallery {
    constructor() {
        this.page = 1;
        this.loading = false;
        this.container = document.getElementById('gallery-container');
        this.observer = new IntersectionObserver(this.handleIntersect.bind(this));
        this.init();
    }
    
    init() {
        this.loadMore();
        const sentinel = document.createElement('div');
        sentinel.id = 'sentinel';
        this.container.appendChild(sentinel);
        this.observer.observe(sentinel);
    }
    
    handleIntersect(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting && !this.loading) {
                this.loadMore();
            }
        });
    }
    
    async loadMore() {
        this.loading = true;
        try {
            const response = await fetch(
                `https://picsum.photos/v2/list?page=${this.page}&limit=30`
            );
            const data = await response.json;
            this.renderImages(data);
            this.page++;
        } catch (error) {
            console.error(error);
        } finally {
            this.loading = false;
        }
    }
    
    renderImages(images) {
        images.forEach(image => {
            const item = document.createElement('div');
            item.className = 'image-item';
            item.innerHTML = `
                <img 
                    src="${image.download_url}" 
                    alt="${image.author}" 
                    loading="lazy"
                    width="300" 
                    height="200"
                >
                <div class="image-author">${image.author}
`; this.container.insertBefore(item, document.getElementById('sentinel')); }); } } new InfiniteGallery();

项目 D:简易在线协作白板

涵盖:Canvas API、WebSocket、事件系统、撤销/重做栈。

功能需求

// app.js
class Whiteboard {
    constructor() {
        this.canvas = document.getElementById('whiteboard');
        this.ctx = this.canvas.getContext('2d');
        this.isDrawing = false;
        this.lastX = 0;
        this.lastY = 0;
        this.color = '#000000';
        this.lineWidth = 2;
        this.history = [];
        this.historyIndex = -1;
        this.init();
    }
    
    init() {
        this.setupCanvas();
        this.bindEvents();
        this.saveState();
    }
    
    setupCanvas() {
        this.canvas.width = window.innerWidth - 40;
        this.canvas.height = window.innerHeight - 100;
        this.ctx.lineCap = 'round';
        this.ctx.lineJoin = 'round';
    }
    
    bindEvents() {
        this.canvas.addEventListener('mousedown', this.startDrawing.bind(this));
        this.canvas.addEventListener('mousemove', this.draw.bind(this));
        this.canvas.addEventListener('mouseup', this.stopDrawing.bind(this));
        this.canvas.addEventListener('mouseout', this.stopDrawing.bind(this));
        
        // 颜色选择
        document.getElementById('color').addEventListener('change', (e) => {
            this.color = e.target.value;
        });
        
        // 线宽选择
        document.getElementById('lineWidth').addEventListener('change', (e) => {
            this.lineWidth = e.target.value;
        });
        
        // 撤销/重做
        document.getElementById('undo').addEventListener('click', this.undo.bind(this));
        document.getElementById('redo').addEventListener('click', this.redo.bind(this));
        
        // 清除画布
        document.getElementById('clear').addEventListener('click', this.clear.bind(this));
    }
    
    startDrawing(e) {
        this.isDrawing = true;
        [this.lastX, this.lastY] = [this.getMousePos(e).x, this.getMousePos(e).y];
    }
    
    draw(e) {
        if (!this.isDrawing) return;
        
        this.ctx.beginPath();
        this.ctx.moveTo(this.lastX, this.lastY);
        
        const currentPos = this.getMousePos(e);
        this.ctx.lineTo(currentPos.x, currentPos.y);
        
        this.ctx.strokeStyle = this.color;
        this.ctx.lineWidth = this.lineWidth;
        this.ctx.stroke();
        
        [this.lastX, this.lastY] = [currentPos.x, currentPos.y];
    }
    
    stopDrawing() {
        if (this.isDrawing) {
            this.isDrawing = false;
            this.saveState();
        }
    }
    
    getMousePos(e) {
        const rect = this.canvas.getBoundingClientRect();
        return {
            x: e.clientX - rect.left,
            y: e.clientY - rect.top
        };
    }
    
    saveState() {
        this.history = this.history.slice(0, this.historyIndex + 1);
        this.history.push(this.canvas.toDataURL());
        this.historyIndex++;
    }
    
    undo() {
        if (this.historyIndex > 0) {
            this.historyIndex--;
            this.restoreState();
        }
    }
    
    redo() {
        if (this.historyIndex < this.history.length - 1) {
            this.historyIndex++;
            this.restoreState();
        }
    }
    
    restoreState() {
        const img = new Image();
        img.onload = () => {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.ctx.drawImage(img, 0, 0);
        };
        img.src = this.history[this.historyIndex];
    }
    
    clear() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.saveState();
    }
}

new Whiteboard();

项目总结

通过完成这些实战项目,你将掌握以下核心技能:

建议:从简单的 Todo List 开始,逐步挑战更复杂的项目。每个项目都尝试添加一些自己的创意和功能,这样可以更好地巩固所学知识。