从零构建待办清单:HTML+Bootstrap+原生JS全栈入门实战

张开发
2026/6/7 15:56:51 15 分钟阅读

分享文章

从零构建待办清单:HTML+Bootstrap+原生JS全栈入门实战
1. 项目概述一个全栈初学者的实战演练场如果你刚刚踏入Web开发的大门面对HTML、CSS、JavaScript这一大堆名词感到无从下手或者已经学了些零散语法却不知道如何把它们拼成一个能跑起来的“真东西”那么你来对地方了。这个“待办事项清单”应用就是为你量身打造的“第一块砖”。它麻雀虽小五脏俱全用HTML搭骨架用Bootstrap这个“化妆师”快速美化界面再用JavaScript注入灵魂让它能响应用户的点击和输入。最后我们还会亲手把它送上云端让全世界都能通过一个链接访问它。整个过程就像看着一堆乐高积木从散件一步步变成一辆能跑的小车这种从零到一的完整感是任何教程片段都无法替代的。无论你是想转行、做课程设计还是单纯想验证自己的学习成果这个项目都能给你一个清晰、可复现的路线图。2. 技术栈选型与核心思路拆解2.1 为什么是“HTML Bootstrap 原生JS”组合在开始敲代码之前我们先聊聊为什么选这套技术方案。对于第一个实战项目核心目标是快速建立正反馈避免在复杂环境中迷失。HTML (HyperText Markup Language)这是网页的“骨骼”和“血肉”。所有你看到的文字、图片、输入框归根结底都是由HTML标签定义的。它是基础中的基础没有它一切无从谈起。我们用它来构建任务列表的容器、输入框和按钮。Bootstrap 3这是网页的“成衣”和“化妆品”。纯手写CSS来让页面美观且适配手机对新手是个巨大挑战。Bootstrap提供了一套预制的、响应式的CSS样式类和组件如按钮、表单、网格系统我们直接“拿来就用”就能让应用在几分钟内变得专业、整洁。选择版本3而非最新的5是因为其语法经典、文档丰富且CDN稳定能最大限度减少环境配置问题。原生JavaScript (Vanilla JS) 与 jQuery这是应用的“大脑”和“神经”。我们需要让页面能“思考”点击“添加”按钮能把输入框的文字变成列表里的一项。这里有个小插曲原教程使用了jQuery。jQuery在过去十年是前端开发的标配它简化了DOM操作和事件处理。但今天现代浏览器原生JavaScriptES6已非常强大且高效。为了更贴近当前行业实践我们的核心逻辑将主要使用原生JavaScript实现同时会对比讲解jQuery的等效写法让你理解两者的联系与区别。这能帮你打下更扎实的JS基础。2.2 应用功能逻辑设计这个待办事项应用的功能极其聚焦符合“最小可行产品”原则任务添加用户在输入框打字点击“添加”按钮新任务项出现在下方列表中。单任务删除每个任务项旁边都有一个删除按钮如“X”点击可移除该单项。批量清空当任务数量积累到一定阈值如3个时显示一个“清空所有”按钮一键清除所有任务。状态反馈界面能动态响应例如输入为空时点击添加应有提示我们将增强这部分清空所有后按钮隐藏。这个清晰的功能列表将直接翻译成我们JavaScript中的事件监听器和函数。3. 从零搭建项目结构与基础页面3.1 初始化项目目录一切从创建一个干净的文件夹开始。打开你的终端Windows上的CMD/PowerShellMac/Linux上的Terminal执行以下命令# 创建一个名为 simple-todolist 的项目文件夹 mkdir simple-todolist # 进入该文件夹 cd simple-todolist接下来在文件夹内创建我们的核心文件。你可以使用命令行也可以在文件管理器中手动创建# 创建首页HTML文件 touch index.html # 创建用于存放JavaScript代码的文件夹和文件 mkdir js touch js/script.js # 可选创建用于存放CSS的文件夹和文件虽然我们主要用Bootstrap但预留位置是个好习惯 mkdir css touch css/style.css完成后你的simple-todolist文件夹结构应如下所示simple-todolist/ ├── index.html ├── js/ │ └── script.js ├── css/ │ └── style.css3.2 编写基础HTML骨架用你喜欢的代码编辑器如VSCode、Sublime Text打开index.html写入以下最基础的HTML5结构!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title我的待办事项清单/title /head body h1我的待办事项清单/h1 /body /html关键点解析!DOCTYPE html声明文档类型为HTML5确保浏览器以标准模式渲染。meta charsetUTF-8设置字符编码为UTF-8这是支持中文和绝大多数字符集的关键能避免乱码。meta nameviewport ...移动端适配的基石。它告诉浏览器视口的宽度等于设备宽度初始缩放比例为1.0这对于Bootstrap实现响应式布局至关重要。title显示在浏览器标签页上的标题。现在双击index.html文件用浏览器打开它。你应该能看到一个巨大而朴素的“我的待办事项清单”标题。恭喜你的第一个网页诞生了4. 引入Bootstrap快速实现响应式美化4.1 通过CDN引入Bootstrap我们不需要下载Bootstrap文件而是通过CDN内容分发网络链接引入这样最快捷。修改index.html的head部分head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title我的待办事项清单/title !-- 引入 Bootstrap 3 CSS 核心样式文件 -- link relstylesheet hrefhttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css !-- 引入我们自己的CSS文件可选用于后续微调 -- link relstylesheet hrefcss/style.css /head同时在/body标签闭合之前引入jQuery和Bootstrap的JavaScript插件文件Bootstrap的一些交互组件如模态框需要JS支持我们的简单应用虽不强制但引入可保持环境完整body ... 你的页面内容 ... !-- 先引入 jQuery -- script srchttps://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.min.js/script !-- 再引入 Bootstrap JS 插件 -- script srchttps://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js/script !-- 最后引入我们自己的JS逻辑文件 -- script srcjs/script.js/script /body注意JavaScript文件的引入顺序很重要。因为Bootstrap的JS插件依赖于jQuery所以jQuery必须在Bootstrap之前加载。而我们自己的script.js通常放在最后以确保它能操作已加载完成的DOM元素。4.2 应用Bootstrap容器与网格系统Bootstrap的核心思想之一是“移动设备优先”的响应式网格系统。我们用容器和行、列来布局。更新body中的内容body div classcontainer !-- container类提供固定的宽度和居中布局 -- div classrow !-- row类代表一行 -- div classcol-xs-12 !-- col-xs-12代表在超小屏幕手机上占满12列即整行 -- h1 classtext-center text-primary我的待办事项清单 small一个简单的实践/small/h1 p classlead text-muted text-center使用 HTML, Bootstrap 和 JavaScript 构建/p hr /div /div !-- 后续的UI组件将在这里添加 -- /div !-- ... 脚本引用 ... -- /body刷新浏览器你会发现标题变小了、居中了并且有了颜色。container、row、col-xs-12、text-center、text-primary这些都是Bootstrap的CSS类它们自动应用了预定义的样式。5. 构建核心用户界面现在我们来构建应用的核心交互区域输入框、按钮和任务列表。5.1 创建输入区域与按钮在hr标签下方继续添加代码。我们规划布局左侧占4列放置输入和操作按钮右侧占8列放置任务列表。div classrow !-- 左侧输入区 -- div classcol-sm-4 div classpanel panel-default div classpanel-heading h3 classpanel-title添加新任务/h3 /div div classpanel-body div classform-group label fornew-task-input任务描述/label input typetext classform-control idnew-task-input placeholder例如学习JavaScript闭包 autofocus p classhelp-block写下你接下来要做的事情。/p /div div classrow div classcol-xs-6 button typebutton classbtn btn-success btn-block idadd-btn span classglyphicon glyphicon-plus/span 添加任务 /button /div div classcol-xs-6 button typebutton classbtn btn-danger btn-block idclear-all-btn styledisplay: none; span classglyphicon glyphicon-trash/span 清空所有 /button /div /div /div /div /div !-- 右侧任务列表区 -- div classcol-sm-8 div classpanel panel-primary div classpanel-heading h3 classpanel-title任务列表/h3 /div div classpanel-body ul classlist-group idtask-list li classlist-group-item list-group-item-info text-center暂无任务请添加。/li !-- 动态添加的任务项将在这里插入 -- /ul div classtext-right text-muted small idtask-counter 当前任务数span idcount0/span /div /div /div /div /divUI设计要点解析面板 (Panel)使用panel类将不同功能模块框起来视觉上更清晰、有层次感。表单控件 (Form Control)form-control类让输入框宽度自适应父容器样式统一。响应式网格col-sm-4和col-sm-8表示在平板及以上屏幕并排显示在手机屏幕小于768px时会自动堆叠。按钮btn-success绿色用于主要操作“添加”btn-danger红色用于危险操作“清空所有”。btn-block让按钮宽度占满父容器。图标 (Glyphicons)Bootstrap 3 内置了图标字体通过glyphicon类使用如glyphicon-plus让按钮更直观。初始状态“清空所有”按钮通过styledisplay: none;初始隐藏。任务列表有一个默认的提示项。任务计数器我们添加了一个区域来动态显示当前任务数量这比原教程的逻辑超过3个显示按钮更直观。现在刷新页面一个像模像样的界面已经出来了但它还不能动接下来我们用JavaScript赋予它生命。6. 用JavaScript实现核心交互逻辑打开js/script.js文件我们将在这里编写所有动态逻辑。我们将采用模块化的方式逐步实现功能。6.1 等待DOM就绪与状态初始化所有操作DOM的代码都必须确保HTML文档已经完全加载和解析。我们使用DOMContentLoaded事件。// 使用原生JS等待DOM加载完成 document.addEventListener(DOMContentLoaded, function() { // 应用状态初始化 let tasks []; // 用数组存储任务数据而不仅仅是计数 const taskListElement document.getElementById(task-list); const newTaskInputElement document.getElementById(new-task-input); const addButtonElement document.getElementById(add-btn); const clearAllButtonElement document.getElementById(clear-all-btn); const taskCountElement document.getElementById(count); // 更新任务计数和清空按钮显示状态的函数 function updateUI() { const count tasks.length; taskCountElement.textContent count; // 根据任务数量决定是否显示“清空所有”按钮 clearAllButtonElement.style.display count 0 ? block : none; // 如果没有任务显示提示信息 if (count 0 !document.querySelector(.list-group-item-info)) { const emptyItem document.createElement(li); emptyItem.className list-group-item list-group-item-info text-center; emptyItem.textContent 暂无任务请添加。; taskListElement.appendChild(emptyItem); } else if (count 0) { const emptyHint document.querySelector(.list-group-item-info); if (emptyHint) { emptyHint.remove(); } } } // 初始化UI updateUI(); // ... 后续的事件监听代码将写在这里 });代码解读DOMContentLoaded比window.onload更早触发当初始HTML文档被完全加载和解析完成时就触发不等待样式表、图像等。状态管理我们用tasks数组来存储任务数据这比只存一个数字更强大未来可以扩展如存储任务ID、完成状态等。缓存DOM元素将频繁操作的元素输入框、按钮、列表用变量缓存起来避免每次操作都去查询DOM提升性能。updateUI函数这是一个关键函数它根据tasks数组的长度来更新页面上的计数器和“清空所有”按钮的显示状态。这种将“状态”与“UI更新”分离的思想是前端开发的核心模式之一。6.2 实现“添加任务”功能在上一段代码的注释位置继续添加“添加任务”的事件监听逻辑// 1. 添加任务功能 function addTask(taskContent) { if (!taskContent || taskContent.trim() ) { // 简单提示可以更优雅地使用Bootstrap的警告框这里先alert alert(任务内容不能为空); newTaskInputElement.focus(); // 焦点回到输入框 return; // 如果内容为空则停止执行 } // 将任务内容添加到状态数组 tasks.push(taskContent.trim()); // 创建新的列表项DOM元素 const listItem document.createElement(li); listItem.className list-group-item; // Bootstrap列表项样式 listItem.setAttribute(data-task, taskContent.trim()); // 存储数据便于查找 // 创建任务文本和删除按钮的容器 const taskContainer document.createElement(div); taskContainer.className task-item; const taskTextSpan document.createElement(span); taskTextSpan.textContent taskContent.trim(); taskTextSpan.className task-text; const deleteButton document.createElement(button); deleteButton.innerHTML span classglyphicon glyphicon-remove/span; deleteButton.className btn btn-xs btn-danger pull-right; deleteButton.title 删除此任务; // 组装元素 taskContainer.appendChild(taskTextSpan); taskContainer.appendChild(deleteButton); listItem.appendChild(taskContainer); // 将新项插入到列表的末尾提示信息之前或直接追加 const emptyHint document.querySelector(.list-group-item-info); if (emptyHint) { taskListElement.insertBefore(listItem, emptyHint); } else { taskListElement.appendChild(listItem); } // 为这个新创建的删除按钮绑定事件 deleteButton.addEventListener(click, function(event) { event.stopPropagation(); // 防止事件冒泡 removeTask(listItem, taskContent.trim()); }); // 清空输入框并聚焦 newTaskInputElement.value ; newTaskInputElement.focus(); // 更新UI状态计数器和按钮 updateUI(); } // 为“添加”按钮绑定点击事件 addButtonElement.addEventListener(click, function() { const taskContent newTaskInputElement.value; addTask(taskContent); }); // 为输入框绑定回车键事件提升用户体验 newTaskInputElement.addEventListener(keypress, function(event) { if (event.key Enter) { event.preventDefault(); // 防止表单提交如果有form标签 addButtonElement.click(); // 触发添加按钮的点击事件 } });功能细节与避坑指南输入验证if (!taskContent || taskContent.trim() )这一行至关重要。它检查输入是否为空或全是空格。.trim()方法能去除字符串首尾的空格避免用户误输入空格创建空任务。DOM操作流程创建元素 (createElement) - 设置属性/类/内容 - 组装 (appendChild) - 插入DOM树 (insertBefore/appendChild)。这是动态创建内容的标准流程。事件委托的考量注意我们是在创建每个删除按钮时单独为它绑定点击事件。这是一种直接的方式。另一种更高效的方式是“事件委托”Event Delegation即把事件监听器绑定在父元素taskListElement上利用事件冒泡来管理所有子按钮的事件。这对于动态添加的大量元素性能更好。为了清晰理解我们先使用直接绑定。后续优化时会提到事件委托。用户体验优化添加了回车键提交功能 (keypress事件监听)这是Web表单的常见习惯能显著提升操作流畅度。6.3 实现“删除单个任务”与“清空所有”功能继续在script.js中添加删除功能的逻辑// 2. 删除单个任务功能 function removeTask(taskElement, taskContent) { // 从状态数组中移除对应的任务 // 注意这里我们根据内容查找并删除。在实际应用中最好使用唯一ID。 const taskIndex tasks.indexOf(taskContent); if (taskIndex -1) { tasks.splice(taskIndex, 1); } // 从DOM中移除该列表项 taskElement.remove(); // 现代浏览器支持的方法等同于 parentNode.removeChild(child) // 更新UI状态 updateUI(); } // 3. 清空所有任务功能 clearAllButtonElement.addEventListener(click, function() { // 二次确认防止误操作 if (tasks.length 0) return; const isConfirmed confirm(确定要删除全部 ${tasks.length} 项任务吗此操作不可撤销。); if (!isConfirmed) return; // 清空状态数组 tasks.length 0; // 快速清空数组 // 清空任务列表的DOM只删除非提示性的任务项 // 方法获取所有类名包含‘list-group-item’但不包含‘list-group-item-info’的li元素 const taskItems taskListElement.querySelectorAll(li.list-group-item:not(.list-group-item-info)); taskItems.forEach(item item.remove()); // 更新UI状态 updateUI(); alert(所有任务已清空。); });安全与体验强化数组操作tasks.splice(taskIndex, 1)用于从数组中删除指定位置的元素。tasks.length 0是清空数组的一种高效方法。DOM删除taskElement.remove()是原生API比传统的parent.removeChild(child)更简洁。防误操作在执行“清空所有”这种破坏性操作前使用confirm()弹窗进行二次确认是良好的实践。在生产环境中可能会使用更美观的自定义模态框Modal。6.4 使用事件委托优化删除功能如前所述为每个动态创建的按钮单独绑定事件在任务项很多时会占用较多内存。我们可以改用事件委托。修改删除功能的实现方式首先移除在addTask函数中为每个deleteButton单独绑定的addEventListener那一行。 然后在DOMContentLoaded的事件监听器内部updateUI();调用之后添加一个针对任务列表的全局点击事件监听// 4. 使用事件委托处理单个任务的删除 taskListElement.addEventListener(click, function(event) { // 检查点击的目标是否是我们想要的删除按钮 // 因为按钮内部可能有图标span所以需要closest来查找最近的.btn-danger const deleteButton event.target.closest(.btn-danger); if (!deleteButton) return; // 如果点击的不是删除按钮则忽略 const listItem deleteButton.closest(.list-group-item); if (!listItem) return; // 获取任务内容这里我们从之前设置的data属性中获取 const taskContent listItem.getAttribute(data-task) || listItem.querySelector(.task-text).textContent; // 调用删除函数 removeTask(listItem, taskContent); });事件委托原理我们只在父容器taskListElement上绑定一个点击事件监听器。当子元素包括动态添加的删除按钮被点击时事件会“冒泡”到父容器。我们通过event.target和closest()方法来判断事件源是否是我们关心的删除按钮。这样做无论未来添加多少任务都只有一个事件监听器性能更优内存占用更少。7. 功能测试、调试与样式微调7.1 功能测试现在打开浏览器尽情测试你的应用吧添加任务在输入框打字点击“添加”按钮或按回车键。观察列表更新计数器变化“清空所有”按钮是否出现。删除单个任务点击某个任务项右边的红色“X”按钮该项应消失计数器减1。清空所有添加几个任务点击红色的“清空所有”按钮确认弹窗后所有任务应被清除按钮隐藏提示信息出现。边界测试尝试添加空内容或空格。清空所有后再尝试删除应无反应。快速连续点击。7.2 浏览器开发者工具调试如果功能不正常请打开浏览器的“开发者工具”F12。Console控制台查看是否有JavaScript报错红色错误信息。这是排查问题的第一站。Elements元素查看生成的HTML结构是否正确CSS类是否应用。Sources源代码可以给你的script.js文件设置断点单步执行观察变量值这是最强大的调试手段。7.3 添加一点自定义样式我们的功能已经完整但可以让它更美观一点。打开css/style.css文件添加一些样式/* css/style.css */ body { padding-top: 20px; background-color: #f8f9fa; /* 一个浅灰色背景 */ } .container { max-width: 900px; /* 限制最大宽度在大屏幕上更友好 */ } .panel { box-shadow: 0 2px 4px rgba(0,0,0,.1); /* 给面板添加轻微阴影 */ border-radius: 4px; /* 圆角 */ } #task-list { min-height: 200px; /* 给列表一个最小高度避免内容太少时布局塌陷 */ } .task-item { display: flex; justify-content: space-between; align-items: center; } .task-text { flex-grow: 1; /* 让任务文本占据剩余空间 */ margin-right: 10px; word-break: break-word; /* 长单词或URL自动换行 */ } .list-group-item-info { font-style: italic; color: #31708f; } hr { border-top-color: #ddd; }在index.html的head中我们已经引入了这个CSS文件。刷新页面看看界面是不是更精致了8. 云端部署让应用在互联网上“活”起来开发完成的应用只躺在你的电脑里现在我们要把它部署到公共网络。我们将使用Vercel原ZEIT Now这个对前端开发者极其友好的平台。它部署静态网站像我们这个只有HTML、CSS、JS的项目完全免费且非常简单。8.1 部署前准备代码版本管理 (Git)虽然Vercel可以直接上传文件夹但使用Git是更专业、更通用的方式。如果你还没有安装Git请先安装。在你的项目根目录 (simple-todolist) 打开终端执行以下命令# 初始化一个新的Git仓库 git init # 将当前目录所有文件添加到暂存区 git add . # 提交你的代码并附上一条描述信息 git commit -m 初始提交完成待办事项应用基础功能8.2 使用Vercel进行一键部署方案A通过Vercel CLI命令行部署推荐安装Vercel CLI确保你已安装Node.js和npm。在终端运行npm install -g vercel登录Vercelvercel login按照提示在打开的浏览器页面中授权登录使用GitHub、GitLab或邮箱注册。部署在项目根目录下运行vercel首次运行会有一系列交互式提问Set up and deploy “~/path/to/simple-todolist”?输入y。Which scope do you want to deploy to?选择你的账户。Link to existing project?输入n创建新项目。What’s your project’s name?可以回车使用默认名或自定义一个如my-simple-todolist。In which directory is your code located?直接回车.表示当前目录。随后Vercel会自动检测到这是一个静态项目并开始部署。发布到生产环境上一步部署会生成一个预览URL。如果你想将其设置为永久的生产环境链接运行vercel --prod部署成功后终端会输出一个类似https://your-project-name.vercel.app的URL。打开它你的应用已经在线了方案B通过Vercel网页界面部署访问 vercel.com 用Git账号登录。点击 “New Project”。选择导入你的Git仓库需要先将代码推送到GitHub/GitLab或者直接拖拽你的simple-todolist文件夹到页面上传。Vercel会自动配置并部署点击生成的域名即可访问。部署核心要点Vercel之所以简单是因为它做到了“零配置”。它能自动识别出你的项目是静态网站并为你配置好HTTP服务器、SSL证书HTTPS、全球CDN。你唯一需要提供的就是你的代码。8.3 部署后维护与迭代现在你的应用已经上线。如果你想修改代码比如修复bug、添加新功能在本地修改index.html,script.js或style.css。使用Git提交更改git add .然后git commit -m “描述修改”。重新运行vercel --prod部署更新。Vercel会生成一个新的、包含这次更改的部署版本。9. 项目总结与扩展思考走到这一步你已经完成了一个完整的Web应用从构思、编码、测试到部署的全流程。这个简单的待办事项就像一把钥匙帮你打开了前端开发世界的大门。回顾一下我们覆盖的核心技能点HTML结构搭建、Bootstrap快速布局、原生JavaScript的DOM操作与事件处理、状态管理思维、以及使用Git和Vercel进行现代化部署。几个可以立刻动手的扩展方向让你的项目更出彩数据持久化现在刷新页面任务就没了。尝试使用浏览器的localStorageAPI将tasks数组保存起来页面加载时再读取实现关掉浏览器再打开任务还在。任务状态管理给每个任务添加一个“完成”复选框。点击后任务文本可以加上删除线 (text-decoration: line-through)。你需要修改数据结构例如tasks数组存储对象{content: ‘...‘, done: false}和updateUI函数。更优雅的交互用Bootstrap的模态框 (modal) 替换alert和confirm弹窗。用Toast提示小型通知来替代“任务已添加”的alert。代码优化将addTask,removeTask,updateUI等函数组织成一个简单的模块或类让代码结构更清晰。这个项目的价值不在于它本身有多复杂而在于它清晰地串联起了前端开发中最核心、最常用的技术链条。当你成功部署并分享链接给朋友时那种成就感是无可替代的。接下来就基于这个坚实的起点去探索更广阔的前端世界吧。

更多文章