从零打造个人博客静态页面与TodoList应用:前端开发实战指南

发布于:2025-05-09 ⋅ 阅读:(12) ⋅ 点赞:(0)

前言

在当今数字时代,拥有个人博客和高效的任务管理工具已成为开发者展示自我和提升生产力的标配。本文将带你从零开始,通过纯前端技术实现一个兼具个人博客静态页面和TodoList任务管理功能的综合应用。无论你是前端新手还是希望巩固基础的中级开发者,这个项目都将为你提供宝贵的实战经验。

一、项目概述与设计

1.1 为什么选择这个组合项目?

博客页面和TodoList看似是两个独立的功能,但它们的组合能带来以下优势:

  • 展示与实用结合:博客展示你的技术思考,TodoList管理你的创作任务

  • 技术覆盖面广:涵盖HTML结构设计、CSS布局美化、JavaScript交互逻辑

  • 可扩展性强:为后续添加后端功能(如用户认证、数据持久化)奠定基础

1.2 技术选型

我们选择纯前端实现方案,确保项目轻量且易于部署:

  • 核心三件套:HTML5 + CSS3 + JavaScript (ES6+)

  • CSS框架:使用Tailwind CSS实现快速样式开发(可选)

  • 图标库:Font Awesome或Remix Icon

  • 部署方案:GitHub Pages/Vercel/Netlify

二、博客静态页面开发

2.1 HTML结构设计

<!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">
        <nav>
            <div class="logo">我的技术博客</div>
            <ul class="nav-links">
                <li><a href="#home">首页</a></li>
                <li><a href="#articles">文章</a></li>
                <li><a href="#projects">项目</a></li>
                <li><a href="#about">关于我</a></li>
                <li><a href="#todo">TodoList</a></li>
            </ul>
        </nav>
    </header>
    
    <main class="blog-container">
        <section id="home" class="hero-section">
            <h1>欢迎来到我的技术博客</h1>
            <p>分享前端开发、算法设计与技术思考</p>
        </section>
        
        <section id="articles" class="articles-section">
            <h2>最新文章</h2>
            <div class="article-card">
                <h3>React Hooks深度解析</h3>
                <p class="meta">发布于2023年5月15日 · 8分钟阅读</p>
                <p>本文将深入探讨React Hooks的工作原理和最佳实践...</p>
                <a href="#" class="read-more">阅读全文</a>
            </div>
            <!-- 更多文章卡片 -->
        </section>
        
        <section id="projects" class="projects-section">
            <!-- 项目展示区 -->
        </section>
    </main>
    
    <footer class="blog-footer">
        <p>© 2023 我的技术博客. 保留所有权利.</p>
    </footer>
    
    <script src="script.js"></script>
</body>
</html>

2.2 CSS样式美化

/* 基础样式 */
:root {
    --primary-color: #3498db;
    --secondary-color: #2ecc71;
    --dark-color: #2c3e50;
    --light-color: #ecf0f1;
    --danger-color: #e74c3c;
}

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

.blog-header {
    background-color: var(--dark-color);
    color: white;
    padding: 1rem 2rem;
    position: sticky;
    top: 0;
    z-index: 100;
}

nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
}

.nav-links {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-links a {
    color: white;
    text-decoration: none;
    transition: color 0.3s;
}

.nav-links a:hover {
    color: var(--primary-color);
}

/* 响应式设计 */
@media (max-width: 768px) {
    nav {
        flex-direction: column;
    }
    
    .nav-links {
        margin-top: 1rem;
        gap: 1rem;
    }
}

/* 文章卡片样式 */
.article-card {
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    padding: 1.5rem;
    margin-bottom: 1.5rem;
    transition: transform 0.3s, box-shadow 0.3s;
}

.article-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.meta {
    color: #7f8c8d;
    font-size: 0.9rem;
}

.read-more {
    display: inline-block;
    color: var(--primary-color);
    text-decoration: none;
    font-weight: bold;
    margin-top: 0.5rem;
}

.read-more:hover {
    text-decoration: underline;
}

三、TodoList应用实现

3.1 HTML结构

在博客页面中添加TodoList部分:

<section id="todo" class="todo-section">
    <h2>任务管理</h2>
    <div class="todo-container">
        <div class="todo-input">
            <input type="text" id="todoInput" placeholder="添加新任务...">
            <button id="addTodoBtn">添加</button>
        </div>
        <div class="todo-filters">
            <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>
        </div>
        <ul id="todoList" class="todo-list">
            <!-- 任务将通过JavaScript动态添加 -->
        </ul>
        <div class="todo-stats">
            <span id="remainingCount">0</span> 个任务待完成
            <button id="clearCompleted" class="clear-btn">清除已完成</button>
        </div>
    </div>
</section>

3.2 JavaScript功能实现

document.addEventListener('DOMContentLoaded', function() {
    // DOM元素
    const todoInput = document.getElementById('todoInput');
    const addTodoBtn = document.getElementById('addTodoBtn');
    const todoList = document.getElementById('todoList');
    const filterBtns = document.querySelectorAll('.filter-btn');
    const remainingCount = document.getElementById('remainingCount');
    const clearCompletedBtn = document.getElementById('clearCompleted');
    
    // 状态管理
    let todos = JSON.parse(localStorage.getItem('todos')) || [];
    let currentFilter = 'all';
    
    // 初始化
    renderTodoList();
    updateRemainingCount();
    
    // 事件监听
    addTodoBtn.addEventListener('click', addTodo);
    todoInput.addEventListener('keypress', function(e) {
        if (e.key === 'Enter') addTodo();
    });
    
    clearCompletedBtn.addEventListener('click', clearCompleted);
    
    filterBtns.forEach(btn => {
        btn.addEventListener('click', function() {
            filterBtns.forEach(b => b.classList.remove('active'));
            this.classList.add('active');
            currentFilter = this.dataset.filter;
            renderTodoList();
        });
    });
    
    // 功能函数
    function addTodo() {
        const text = todoInput.value.trim();
        if (text) {
            const newTodo = {
                id: Date.now(),
                text,
                completed: false,
                createdAt: new Date().toISOString()
            };
            
            todos.unshift(newTodo);
            saveTodos();
            renderTodoList();
            updateRemainingCount();
            todoInput.value = '';
        }
    }
    
    function renderTodoList() {
        todoList.innerHTML = '';
        
        const filteredTodos = todos.filter(todo => {
            if (currentFilter === 'all') return true;
            if (currentFilter === 'active') return !todo.completed;
            if (currentFilter === 'completed') return todo.completed;
            return true;
        });
        
        if (filteredTodos.length === 0) {
            todoList.innerHTML = '<li class="empty-message">暂无任务</li>';
            return;
        }
        
        filteredTodos.forEach(todo => {
            const li = document.createElement('li');
            li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
            li.dataset.id = todo.id;
            
            li.innerHTML = `
                <input type="checkbox" ${todo.completed ? 'checked' : ''}>
                <span class="todo-text">${todo.text}</span>
                <button class="delete-btn">×</button>
                <small class="todo-date">${formatDate(todo.createdAt)}</small>
            `;
            
            const checkbox = li.querySelector('input[type="checkbox"]');
            const deleteBtn = li.querySelector('.delete-btn');
            
            checkbox.addEventListener('change', function() {
                toggleTodoComplete(todo.id);
            });
            
            deleteBtn.addEventListener('click', function() {
                deleteTodo(todo.id);
            });
            
            todoList.appendChild(li);
        });
    }
    
    function toggleTodoComplete(id) {
        todos = todos.map(todo => 
            todo.id === id ? {...todo, completed: !todo.completed} : todo
        );
        saveTodos();
        renderTodoList();
        updateRemainingCount();
    }
    
    function deleteTodo(id) {
        todos = todos.filter(todo => todo.id !== id);
        saveTodos();
        renderTodoList();
        updateRemainingCount();
    }
    
    function clearCompleted() {
        todos = todos.filter(todo => !todo.completed);
        saveTodos();
        renderTodoList();
    }
    
    function updateRemainingCount() {
        const count = todos.filter(todo => !todo.completed).length;
        remainingCount.textContent = count;
    }
    
    function saveTodos() {
        localStorage.setItem('todos', JSON.stringify(todos));
    }
    
    function formatDate(dateString) {
        const options = { year: 'numeric', month: 'short', day: 'numeric' };
        return new Date(dateString).toLocaleDateString('zh-CN', options);
    }
});

3.3 TodoList样式补充

/* TodoList 样式 */
.todo-section {
    background-color: #f8f9fa;
    padding: 2rem;
    border-radius: 8px;
    margin-top: 2rem;
}

.todo-container {
    max-width: 600px;
    margin: 0 auto;
    background: white;
    padding: 1.5rem;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}

.todo-input {
    display: flex;
    margin-bottom: 1rem;
    gap: 0.5rem;
}

.todo-input input {
    flex: 1;
    padding: 0.75rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 1rem;
}

.todo-input button {
    padding: 0.75rem 1.5rem;
    background-color: var(--primary-color);
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

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

.todo-filters {
    display: flex;
    gap: 0.5rem;
    margin-bottom: 1rem;
}

.filter-btn {
    padding: 0.5rem 1rem;
    background: none;
    border: 1px solid #ddd;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.3s;
}

.filter-btn.active {
    background-color: var(--primary-color);
    color: white;
    border-color: var(--primary-color);
}

.todo-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.todo-item {
    display: flex;
    align-items: center;
    padding: 0.75rem;
    border-bottom: 1px solid #eee;
    gap: 0.75rem;
}

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

.todo-item input[type="checkbox"] {
    cursor: pointer;
}

.todo-text {
    flex: 1;
}

.delete-btn {
    background: none;
    border: none;
    color: var(--danger-color);
    font-size: 1.25rem;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.3s;
}

.delete-btn:hover {
    opacity: 1;
}

.todo-date {
    color: #95a5a6;
    font-size: 0.8rem;
}

.empty-message {
    text-align: center;
    color: #95a5a6;
    padding: 1rem;
}

.todo-stats {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 1rem;
    color: #7f8c8d;
    font-size: 0.9rem;
}

.clear-btn {
    background: none;
    border: none;
    color: var(--danger-color);
    cursor: pointer;
    font-size: 0.9rem;
}

.clear-btn:hover {
    text-decoration: underline;
}

四、项目优化与高级功能

4.1 性能优化建议

  1. 图片懒加载:对博客中的图片实现懒加载

<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy-load">

// 实现懒加载
const lazyImages = document.querySelectorAll('.lazy-load');

const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            img.classList.remove('lazy-load');
            observer.unobserve(img);
        }
    });
});

lazyImages.forEach(img => imageObserver.observe(img));

    2. 防抖处理:对搜索功能或频繁触发的事件添加防抖

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// 使用示例
searchInput.addEventListener('input', debounce(function() {
    // 搜索逻辑
}, 300));

4.2 可添加的高级功能

  1. Markdown支持:让博客支持Markdown格式

// 使用marked.js库
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

// 转换Markdown为HTML
document.getElementById('markdown-content').innerHTML = 
    marked.parse(markdownText);

   2. 主题切换:实现暗黑/明亮模式切换

/* 在:root中添加CSS变量 */
:root {
    --bg-color: #ffffff;
    --text-color: #333333;
    /* 其他变量 */
}

/* 暗黑模式 */
[data-theme="dark"] {
    --bg-color: #1a1a1a;
    --text-color: #f0f0f0;
    /* 其他变量 */
}
// 切换主题
function toggleTheme() {
    const currentTheme = document.documentElement.getAttribute('data-theme');
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
}

// 初始化主题
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);

  3. PWA支持:让应用可离线使用

  • 创建manifest.json文件

  • 注册Service Worker

五、项目部署指南

5.1 GitHub Pages部署

  1. 在GitHub创建新仓库

  2. 将项目代码推送到仓库

  3. 进入仓库Settings > Pages

  4. 选择部署分支(通常是main或master)

  5. 等待几分钟,访问提供的URL

5.2 Vercel部署(更推荐)

  1. 注册Vercel账号(可使用GitHub账号登录)

  2. 点击"New Project"

  3. 导入你的GitHub仓库

  4. 配置项目(保持默认即可)

  5. 点击"Deploy"

  6. 部署完成后会自动获得一个vercel.app的域名

六、总结与扩展方向

通过本项目,你已经掌握了:

  • 响应式博客页面的设计与实现

  • 功能完整的TodoList应用开发

  • 本地存储(localStorage)的使用

  • 前端状态管理的基本概念

  • 项目部署的基本流程

扩展方向建议

  1. 添加后端支持:使用Node.js + Express或Python Flask为项目添加后端API

  2. 数据库集成:使用MongoDB或Firebase存储博客文章和任务数据

  3. 用户认证:实现登录注册功能,让TodoList可以多用户使用

  4. 博客管理系统:开发一个简易的CMS用于管理博客内容

  5. 评论系统:为博客添加


网站公告

今日签到

点亮在社区的每一天
去签到