一、前言

作为前端新手入门实战项目,「随身便签」是一个绝佳的选择 —— 它涵盖了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 样式:打造高颜值界面

重点优化点:

  1. 视觉质感:主容器使用圆角 + 阴影模拟 APP 卡片,背景渐变提升层次感;
  2. 交互动效:按钮 hover 上移、输入框聚焦发光、便签 hover 阴影,提升体验;
  3. 响应式适配:通过@media适配小屏手机,保证移动端体验;
  4. 细节美化:渐变文字、美化分割线、自定义滚动条,细节拉满。

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)事件绑定

  • 点击 “添加” 按钮触发新增;
  • 输入框按回车触发新增;
  • 页面加载时自动渲染列表 + 聚焦输入框。

五、功能演示

  1. 运行方式:将代码保存为.html文件,双击用浏览器打开即可;
  2. 新增便签:输入内容后点击 “+ 添加” 或按回车键,便签立即出现在列表;
  3. 编辑便签:点击任意便签,弹出编辑框修改内容,点击 “确定” 自动保存;
  4. 删除便签:长按任意便签(PC 端按住鼠标 500ms,移动端长按),确认后删除;
  5. 数据持久化:关闭浏览器重新打开,之前添加的便签依然存在;
  6. 空状态提示:无便签时显示 “暂无便签,点击添加吧~”,并提示操作方式。

六、总结与扩展

6.1 核心知识点回顾

本次实战覆盖了前端入门的核心技能:

  • DOM 操作(创建 / 删除元素、事件绑定);
  • 本地存储(localStorage 的增删改查、JSON 序列化 / 反序列化);
  • 响应式布局(@media 媒体查询);
  • 交互优化(动效、长按判定、空状态引导)。

6.2 扩展方向(进阶练习)

  1. 增加便签分类(工作 / 生活 / 学习);
  2. 实现便签搜索功能;
  3. 自定义便签颜色 / 字体;
  4. 增加便签置顶 / 排序功能;
  5. 导出便签为 TXT/JSON 文件。

这个随身便签项目代码简洁、功能完整,既适合新手入门练手,也可以作为课程作业 / 个人作品集的小项目。如果有任何问题,欢迎在评论区交流~

Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐