> 当指尖划过冷硬的电子屏幕,你是否也曾怀念作业本上铅笔勾勒的温暖线条?在这个万物皆可数字化的时代,为你带来一款会呼吸的待办事项本——它用CSS绘制出水彩笔触,让JavaScript跳动出童趣韵律,在代码世界复现手账本般的治愈体验。这不是普通的效率工具,而是一封写给数字原住民的复古情书,现在,让我们揭开手绘风待办应用的魔法秘籍! 快来实现你的一个的待办记事本吧!
待办事项记事本JavaScript代码:
// 获取DOM元素
const form = document.querySelector('.form'); // 获取表单元素
const input = document.querySelector('.form_input'); // 获取输入框
const ul = document.querySelector('.todo-list'); // 获取待办事项列表容器
// 数据存储
const toDoListArray = []; // 创建空数组存储待办事项(数据层)
// 表单提交事件监听
form.addEventListener('submit', function (e) { // 监听表单提交事件
e.preventDefault(); // 阻止表单默认提交行为(避免页面刷新)
// 生成唯一ID(使用时间戳确保每个任务有唯一标识)
let itemId = String(Date.now());
// 获取用户输入内容
let toDoItem = input.value;
// 添加任务到数据层和视图层
addItemToArray(itemId, toDoItem); // 存储到数组
addItemToDom(itemId, toDoItem); // 添加到DOM
input.value = ''; // 清空输入框(准备接收新输入)
})
// 添加任务到数据数组
function addItemToArray (id, item) {
toDoListArray.push({ // 将新任务对象推入数组
itemId: id, // 存储唯一ID
todoItem: item // 存储任务内容
})
}
// 添加任务到DOM视图
function addItemToDom (id, item) {
const li = document.createElement('li'); // 创建新的列表项元素
li.textContent = item; // 设置列表项文本内容
li.setAttribute('data-id', id); // 设置自定义属性存储ID(用于后续操作)
ul.appendChild(li); // 将新项添加到列表末尾
}
// 任务删除功能(事件委托)
ul.addEventListener('click', function(e) {
// 获取被点击元素的data-id属性(事件委托的关键)
const id = e.target.getAttribute('data-id');
// 检查是否有效ID(避免误点击空白区域)
if (id) {
removeItemFromArray(id); // 从数据数组中移除
removeItemFromDom(id); // 从DOM中移除
}
})
// 从数据数组中移除任务
function removeItemFromArray (id) {
// 使用filter方法创建新数组(排除指定ID的任务)
toDoListArray = toDoListArray.filter(item => item.itemId !== id);
}
// 从DOM中移除任务元素
function removeItemFromDom (id) {
// 通过data-id选择器找到对应元素
let li = document.querySelector(`li[data-id="${id}"]`);
// 从父元素中移除该节点
ul.removeChild(li);
}
关键代码段解析
事件委托模式:
ul.addEventListener('click', function(e) {
const id = e.target.getAttribute('data-id');
// ...
})
- 原理:在父元素(ul)上设置事件监听器,而非每个子元素(li)
- 优势:处理动态添加元素,减少内存占用,提高性能
- 工作流程:点击事件冒泡到ul → 通过e.target定位实际点击元素 → 通过data-id获取关联数据
数据-DOM绑定:
li.setAttribute('data-id', id);
- 作用:在DOM元素上存储对应数据的唯一标识
- 用途:实现数据层与视图层的双向关联
- 优势:删除操作时能准确定位数据和DOM元素 函数职责分离:
// 数据操作
function addItemToArray(id, item) { ... }
// 视图操作
function addItemToDom(id, item) { ... }
删除逻辑实现:
toDoListArray.filter(item => item.itemId !== id);
- 原理:使用filter创建新数组,排除指定ID的项目
- 替代方案:findIndex + splice(直接修改原数组)
- 选择原因:更函数式,避免直接修改原数组
待办事项记事本html代码:
<!DOCTYPE html> <!-- 声明文档类型为HTML5 -->
<html lang="en"> <!-- HTML文档根元素,设置语言为英语 -->
<head>
<!-- 字符编码设置:确保正确显示各种字符 -->
<meta charset="UTF-8">
<!-- 视口设置:使页面在移动设备上正确缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面标题:显示在浏览器标签页上 -->
<title>我的便签</title>
<!-- 外部样式表链接:引入样式文件 -->
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- 主容器:包裹整个应用内容 -->
<div class="container">
<!-- 标题区域:应用标题 -->
<div class="heading">
<!-- 主标题:应用名称 -->
<h1 class="heading_title">To-Do List</h1>
</div>
<!-- 表单区域:用于添加新待办事项 -->
<form class="form">
<div>
<!-- 输入框标签:描述输入框用途 -->
<label for="todo" class="form_label">~ 今天干些什么呢? ~</label>
<!-- 文本输入框:用户输入待办事项内容 -->
<input
type="text"
class="form_input"
id="todo" <!-- 与label的for属性关联 -->
size="30" <!-- 初始宽度 -->
required <!-- 必填字段验证 -->
>
<!-- 提交按钮:添加新待办事项 -->
<button class="button">
<span>Submit</span>
</button>
</div>
</form>
<!-- 待办事项列表区域 -->
<div>
<!-- 无序列表:显示所有待办事项 -->
<ul class="todo-list">
<!-- 示例待办事项 -->
<li>玩游戏</li>
</ul>
</div>
</div>
<!-- 外部JavaScript文件链接:引入交互功能 -->
<script src="./index.js"></script>
</body>
</html>
关键部分详细说明:
<meta name="viewport">
- 确保页面在各种设备上正确显示
width=device-width:使页面宽度等于设备宽度initial-scale=1.0:设置初始缩放级别为100%
<link rel="stylesheet">
- 引入外部CSS文件
style.css - 分离样式和结构,提高可维护性
<form class="form">
- 表单容器,用于收集用户输入
- 包含输入框和提交按钮
required属性确保用户必须输入内容
<label for="todo">
- 关联标签和输入框,提高可访问性
- 点击标签可自动聚焦到输入框
<ul class="todo-list">
- 无序列表容器,用于动态显示待办事项
- JavaScript将在此添加/删除列表项
<script src="./index.js">
- 引入JavaScript文件,添加交互功能
- 位置在
</body>前确保DOM加载完成 - 处理表单提交和列表操作
这个HTML结构清晰划分了三个主要区域:
- 标题区:展示应用名称
- 输入区:添加新待办事项
- 列表区:展示所有待办事项
待办事项记事本css代码:
/* 引入Google Fonts的手写字体 */
@import url(https://fonts.googleapis.com/css?family=Gochi+Hand);
/* 页面整体样式 */
body {
background-color: pink; /* 设置粉色背景 */
min-height: 100vh; /* 最小高度为视口高度 */
display: flex; /* 使用Flex布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
font-family: "Gochi Hand", cursive; /* 使用手写字体 */
color: #494a4b; /* 文字颜色 */
text-align: center; /* 文本居中 */
font-size: 130%; /* 增大基础字体大小 */
}
/* 主容器样式 */
.container {
width: 100%; /* 宽度100% */
min-width: 250px; /* 最小宽度 */
max-width: 500px; /* 最大宽度 */
min-height: 500px; /* 最小高度 */
background-color: #f1f5f8; /* 浅灰色背景 */
box-shadow: 4px 3px 7px 2px #000; /* 添加阴影效果 */
border-radius: 20px; /* 圆角边框 */
padding: 1rem; /* 内边距 */
box-sizing: border-box; /* 盒模型计算方式 */
/* 背景点状纹理 */
background-image: radial-gradient(#bfc0c1 7.2%, transparent 0);
background-size: 25px 25px; /* 点阵大小 */
}
/* 标题区域样式 */
.heading {
margin-bottom: 1rem; /* 底部外边距 */
}
/* 主标题样式 */
.heading_title {
padding: 0.2rem 1.2rem; /* 内边距 */
background-color: #95a9ea; /* 浅蓝色背景 */
border-radius: 20% 5% 20% 5%/5% 20% 25% 20%; /* 不规则圆角 */
transform: rotate(3deg); /* 轻微旋转 */
display: inline-block; /* 行内块元素 */
}
/* 表单标签样式 */
.form_label {
display: block; /* 块级元素 */
margin-bottom: 0.5rem; /* 底部外边距 */
}
/* 输入框样式 */
.form_input {
box-sizing: border-box; /* 盒模型计算方式 */
background-color: transparent; /* 透明背景 */
padding: 0.7rem; /* 内边距 */
border: 3px solid transparent; /* 透明边框 */
/* 特殊的手绘风格边框 */
border-bottom-right-radius: 15px 3px; /* 右下角不规则圆角 */
border-bottom-left-radius: 3px 15px; /* 左下角不规则圆角 */
border-bottom: dashed 3px #95a9ea; /* 底部虚线边框 */
font-family: "Gochi Hand", cursive; /* 手写字体 */
font-size: 1rem; /* 字体大小 */
color: rgba(63, 62, 65, 0.7); /* 半透明文字颜色 */
width: 70%; /* 宽度 */
margin-bottom: 20px; /* 底部外边距 */
}
/* 输入框聚焦状态 */
.form_input:focus {
outline: none; /* 移除默认轮廓 */
border: 3px solid #95a9ea; /* 实线边框 */
}
/* 按钮基础样式 */
.button {
padding: 0; /* 无内边距 */
border: none; /* 无边框 */
font-family: "Gochi Hand", cursive; /* 手写字体 */
padding-bottom: 3px; /* 底部内边距 */
/* 背景纹理(base64图片数据) */
background-image: url("data:image/gif;base64,R0lGODlhBAAEAIAB...");
border-radius: 5px; /* 圆角 */
box-shadow: 0 2px 0 #494a4b; /* 阴影效果 */
background-color: rgba(0, 119, 255, 0.7); /* 半透明蓝色背景 */
/* 动画效果 */
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transform: rotate(4deg); /* 轻微旋转 */
}
/* 按钮内部span样式 */
.button span {
background-color: #f1f5f8; /* 浅灰色背景 */
display: block; /* 块级元素 */
padding: 0.5rem 1rem; /* 内边距 */
border-radius: 5px; /* 圆角 */
border: 2px solid #494a4b; /* 边框 */
}
/* 按钮激活/聚焦状态 */
.button:active,
.button:focus {
padding-bottom: 0; /* 减少底部内边距 */
outline: 0; /* 移除轮廓 */
transform: rotate(0deg); /* 旋转回正 */
}
/* 待办事项列表 */
.todo-list {
text-align: left; /* 左对齐文本 */
}
/* 列表项样式 */
.todo-list li {
padding: 0.5rem; /* 内边距 */
}
/* 列表项悬停效果 */
.todo-list li:hover {
text-decoration: line-through wavy red; /* 波浪形删除线 */
}
关键样式解析:
背景点阵效果
background-image: radial-gradient(#bfc0c1 7.2%, transparent 0);
background-size: 25px 25px;
- 使用径向渐变创建点状背景
- 模拟真实便签纸的印刷纹理
手绘风格边框
border-bottom-right-radius: 15px 3px;
border-bottom-left-radius: 3px 15px;
border-bottom: dashed 3px #95a9ea;
- 不对称圆角设计模仿手绘不规则性
- 虚线边框模拟手绘线条
按钮动画效果
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transform: rotate(4deg);
- 自定义贝塞尔曲线实现弹性动画
- 轻微旋转创造手绘随意感
波浪形删除线
text-decoration: line-through wavy red;
- 悬停时显示红色波浪线
- 视觉暗示任务完成状态
响应式设计
min-width: 250px;
max-width: 500px;
- 确保在手机和桌面设备上都能良好显示
- 限制最大宽度避免在大屏幕上过宽
3D按钮效果
box-shadow: 0 2px 0 #494a4b;
padding-bottom: 3px;
- 创建按钮的立体感
- 点击时通过减少padding实现按下效果
> 当最后一行代码化作屏幕上的温柔笔触,我们完成的不仅是一个应用,更是一场数字时代的温柔革命。那些跃动的便签纸、会呼吸的虚线框、带着体温的挤压动画,都在诉说着同一个故事:科技从未远离手作的温度。此刻,这个会画画、懂心跳的待办本正静静躺在云端,等待与你的创意灵魂相遇。不如现在就打开编辑器,让指尖在键盘上跳一支圆舞曲——毕竟,最动人的代码,永远是写给生活的情书