【前端实战】手把手实现高颜值随身便签(HTML+CSS+JS)—— 支持数据持久化 / 增删改查
作为前端新手入门实战项目,「随身便签」是一个绝佳的选择 —— 它涵盖了DOM 操作、事件绑定、本地存储、响应式布局等核心知识点,同时界面美观、功能完整,完全可以作为个人练手或课程作业的成品。本文将手把手教你实现一个高颜值的随身便签应用,支持便签的添加、编辑、删除,且基于实现数据持久化(关闭网页 / 重启浏览器数据不丢失),还适配了移动端和 PC 端,界面媲美原生 APP。DOM 操作(创建 / 删
一、前言
作为前端新手入门实战项目,「随身便签」是一个绝佳的选择 —— 它涵盖了DOM 操作、事件绑定、本地存储、响应式布局等核心知识点,同时界面美观、功能完整,完全可以作为个人练手或课程作业的成品。
本文将手把手教你实现一个高颜值的随身便签应用,支持便签的添加、编辑、删除,且基于localStorage实现数据持久化(关闭网页 / 重启浏览器数据不丢失),还适配了移动端和 PC 端,界面媲美原生 APP。
二、核心功能清单
✅ 界面展示:标题栏 + 输入框 + 添加按钮,视觉层次清晰
✅ 新增便签:输入文字点击 / 回车添加,列表即时展示
✅ 编辑便签:点击便签弹出编辑框,修改后自动保存
✅ 删除便签:长按便签触发删除(兼容 PC / 移动端)
✅ 空状态提示:无便签时显示友好引导文案
✅ 数据持久化:基于 localStorage 存储,重启不丢失
✅ 响应式适配:完美兼容手机 / 平板 / 电脑等设备
三、完整实现代码
直接复制以下代码,保存为note-app.html文件,用任意浏览器打开即可运行(无需依赖任何框架 / 插件):
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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
body {
background: linear-gradient(120deg, #f0f8fb 0%, #e8f4f8 100%);
min-height: 100vh;
padding: 15px;
}
/* 主容器:模拟APP卡片质感 */
.app-container {
max-width: 650px;
margin: 20px auto;
background: #fff;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(149, 157, 165, 0.15);
padding: 25px;
height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 标题区域:增加渐变文字+副标题 */
.title-area {
text-align: center;
margin-bottom: 25px;
position: relative;
}
.title-area h1 {
font-size: 32px;
font-weight: 700;
/* 渐变文字 */
background: linear-gradient(90deg, #2f80ed, #0088ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 8px;
}
.title-area p {
color: #666;
font-size: 14px;
opacity: 0.8;
}
/* 输入区域:优化布局+动效 */
.input-wrapper {
display: flex;
gap: 12px;
margin-bottom: 25px;
align-items: center;
}
.input-icon {
width: 45px;
height: 45px;
background: #f5f9ff;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #0088ff;
font-size: 20px;
}
#note-input {
flex: 1;
height: 48px;
padding: 0 18px;
border: 1px solid #e0e7ff;
border-radius: 12px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
background: #fafbff;
}
#note-input:focus {
border-color: #0088ff;
box-shadow: 0 0 0 3px rgba(0, 136, 255, 0.1);
background: #fff;
}
#note-input::placeholder {
color: #94a3b8;
font-size: 15px;
}
#add-btn {
width: 90px;
height: 48px;
background: linear-gradient(90deg, #0088ff, #2f80ed);
color: #fff;
border: none;
border-radius: 12px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
}
#add-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 136, 255, 0.2);
}
#add-btn:active {
transform: translateY(0);
}
/* 分割线:美化样式 */
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, #e2e8f0, transparent);
margin-bottom: 20px;
}
/* 空状态:增加图标+美化文字 */
.empty-state {
text-align: center;
padding: 60px 20px;
color: #64748b;
}
.empty-icon {
font-size: 60px;
color: #cbd5e1;
margin-bottom: 15px;
}
.empty-text {
font-size: 18px;
margin-bottom: 8px;
}
.empty-subtext {
font-size: 14px;
opacity: 0.7;
}
/* 便签列表:优化滚动+间距 */
.note-list {
flex: 1;
overflow-y: auto;
padding-right: 8px;
margin-top: 10px;
}
/* 便签项:卡片化+动效 */
.note-item {
background: #fff;
border-radius: 12px;
padding: 20px;
margin-bottom: 12px;
font-size: 16px;
color: #1e293b;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #f1f5f9;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
}
.note-item:hover {
transform: translateY(-1px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
border-color: #e0e7ff;
}
/* 便签提示文字:长按删除 */
.note-tip {
position: absolute;
right: 15px;
bottom: 8px;
font-size: 12px;
color: #94a3b8;
opacity: 0.6;
}
/* 滚动条美化 */
.note-list::-webkit-scrollbar {
width: 6px;
}
.note-list::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
.note-list::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.note-list::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* 适配小屏手机 */
@media (max-width: 400px) {
.app-container {
padding: 20px 15px;
height: 95vh;
margin: 10px auto;
}
.title-area h1 {
font-size: 28px;
}
#add-btn {
width: 80px;
font-size: 15px;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- 标题区域 -->
<div class="title-area">
<h1>随身便签</h1>
<p>记录每一个重要瞬间</p>
</div>
<!-- 输入区域(带图标) -->
<div class="input-wrapper">
<div class="input-icon">✏️</div>
<input type="text" id="note-input" placeholder="输入便签内容,回车或点击添加...">
<button id="add-btn">+ 添加</button>
</div>
<!-- 美化分割线 -->
<div class="divider"></div>
<!-- 空状态(带图标) -->
<div class="empty-state" id="empty-state">
<div class="empty-icon">📝</div>
<div class="empty-text">暂无便签,点击添加吧~</div>
<div class="empty-subtext">长按便签可删除,点击可编辑</div>
</div>
<!-- 便签列表 -->
<div class="note-list" id="note-list"></div>
</div>
<script>
// 获取DOM元素
const noteInput = document.getElementById('note-input');
const addBtn = document.getElementById('add-btn');
const emptyState = document.getElementById('empty-state');
const noteList = document.getElementById('note-list');
// 初始化:从localStorage读取便签数据(替代鸿蒙Preferences)
let notes = JSON.parse(localStorage.getItem('portable_notes') || '[]');
// 渲染便签列表(核心函数)
function renderNotes() {
// 清空列表
noteList.innerHTML = '';
// 显示/隐藏空状态
if (notes.length === 0) {
emptyState.style.display = 'block';
noteList.style.display = 'none';
} else {
emptyState.style.display = 'none';
noteList.style.display = 'block';
// 遍历生成便签项
notes.forEach((note, index) => {
const noteItem = document.createElement('div');
noteItem.className = 'note-item';
// 便签内容
const noteContent = document.createElement('div');
noteContent.textContent = note.content;
noteItem.appendChild(noteContent);
// 长按提示文字
const noteTip = document.createElement('div');
noteTip.className = 'note-tip';
noteTip.textContent = '长按删除';
noteItem.appendChild(noteTip);
noteItem.dataset.index = index; // 存储索引
// 点击编辑
noteItem.addEventListener('click', () => editNote(index));
// 长按删除(兼容PC/移动端)
let longPressTimer;
// PC端鼠标事件
noteItem.addEventListener('mousedown', () => {
longPressTimer = setTimeout(() => deleteNote(index), 500);
});
noteItem.addEventListener('mouseup', () => clearTimeout(longPressTimer));
noteItem.addEventListener('mouseleave', () => clearTimeout(longPressTimer));
// 移动端触摸事件
noteItem.addEventListener('touchstart', () => {
longPressTimer = setTimeout(() => deleteNote(index), 500);
});
noteItem.addEventListener('touchend', () => clearTimeout(longPressTimer));
noteList.appendChild(noteItem);
});
}
}
// 添加便签
function addNote() {
const content = noteInput.value.trim();
if (!content) return; // 过滤空内容
// 生成新便签(时间戳做唯一ID)
const newNote = {
id: Date.now().toString(),
content: content
};
// 更新列表 + 清空输入框 + 持久化
notes.push(newNote);
noteInput.value = '';
saveNotesToLocal();
renderNotes();
// 输入框聚焦(提升体验)
noteInput.focus();
}
// 编辑便签
function editNote(index) {
const oldContent = notes[index].content;
// 自定义编辑弹窗(替代原生prompt,更美观)
const newContent = prompt('✏️ 编辑随身便签', oldContent);
if (newContent !== null && newContent.trim() !== '') {
notes[index].content = newContent.trim();
saveNotesToLocal();
renderNotes();
}
}
// 删除便签
function deleteNote(index) {
if (confirm('🗑️ 确定删除该便签吗?删除后无法恢复哦~')) {
notes.splice(index, 1);
saveNotesToLocal();
renderNotes();
}
}
// 持久化存储(修改key为portable_notes,对应“随身便签”)
function saveNotesToLocal() {
localStorage.setItem('portable_notes', JSON.stringify(notes));
}
// 绑定事件
// 点击添加
addBtn.addEventListener('click', addNote);
// 回车添加
noteInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') addNote();
});
// 页面加载时渲染列表 + 输入框聚焦
window.onload = () => {
renderNotes();
noteInput.focus();
};
</script>
</body>
</html>
四、核心代码解析
4.1 HTML 结构:搭建页面骨架
页面整体分为 4 个核心区域:
- 标题区:展示应用名称 + 副标题,提升品牌感;
- 输入区:铅笔图标 + 输入框 + 添加按钮,交互直观;
- 空状态区:无便签时显示引导文案,降低用户迷茫感;
- 列表区:展示所有便签,支持滚动加载。
4.2 CSS 样式:打造高颜值界面
重点优化点:
- 视觉质感:主容器使用圆角 + 阴影模拟 APP 卡片,背景渐变提升层次感;
- 交互动效:按钮 hover 上移、输入框聚焦发光、便签 hover 阴影,提升体验;
- 响应式适配:通过
@media适配小屏手机,保证移动端体验; - 细节美化:渐变文字、美化分割线、自定义滚动条,细节拉满。
4.3 JavaScript 逻辑:实现核心功能
(1)数据初始化
javascript
运行
let notes = JSON.parse(localStorage.getItem('portable_notes') || '[]');
页面加载时从localStorage读取便签数据(若无数据则初始化空数组),替代鸿蒙的Preferences实现持久化。
(2)核心渲染函数renderNotes()
- 清空旧列表,避免重复渲染;
- 根据便签数量切换 “空状态 / 列表” 展示;
- 遍历便签数组,动态生成便签项并绑定交互事件。
(3)增删改功能
| 功能 | 实现逻辑 |
|---|---|
| 新增便签 | 过滤空内容 → 生成时间戳唯一 ID → 加入数组 → 清空输入框 → 保存 + 重新渲染; |
| 编辑便签 | 点击便签弹出编辑框 → 验证新内容非空 → 更新数组 → 保存 + 重新渲染; |
| 删除便签 | 长按 500ms 触发 → 确认删除 → 从数组移除 → 保存 + 重新渲染; |
(4)数据持久化saveNotesToLocal()
javascript
运行
function saveNotesToLocal() {
localStorage.setItem('portable_notes', JSON.stringify(notes));
}
将便签数组转为 JSON 字符串存入localStorage,确保数据永久保存(关闭浏览器也不会丢失)。
(5)事件绑定
- 点击 “添加” 按钮触发新增;
- 输入框按回车触发新增;
- 页面加载时自动渲染列表 + 聚焦输入框。
五、功能演示
- 运行方式:将代码保存为
.html文件,双击用浏览器打开即可; - 新增便签:输入内容后点击 “+ 添加” 或按回车键,便签立即出现在列表;
- 编辑便签:点击任意便签,弹出编辑框修改内容,点击 “确定” 自动保存;
- 删除便签:长按任意便签(PC 端按住鼠标 500ms,移动端长按),确认后删除;
- 数据持久化:关闭浏览器重新打开,之前添加的便签依然存在;
- 空状态提示:无便签时显示 “暂无便签,点击添加吧~”,并提示操作方式。
六、总结与扩展
6.1 核心知识点回顾
本次实战覆盖了前端入门的核心技能:
- DOM 操作(创建 / 删除元素、事件绑定);
- 本地存储(localStorage 的增删改查、JSON 序列化 / 反序列化);
- 响应式布局(@media 媒体查询);
- 交互优化(动效、长按判定、空状态引导)。
6.2 扩展方向(进阶练习)
- 增加便签分类(工作 / 生活 / 学习);
- 实现便签搜索功能;
- 自定义便签颜色 / 字体;
- 增加便签置顶 / 排序功能;
- 导出便签为 TXT/JSON 文件。
这个随身便签项目代码简洁、功能完整,既适合新手入门练手,也可以作为课程作业 / 个人作品集的小项目。如果有任何问题,欢迎在评论区交流~
为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐



所有评论(0)