网页版便签应用开发:HTML5本地存储与拖拽交互实践

发布于:2025-06-10 ⋅ 阅读:(12) ⋅ 点赞:(0)

摘要

本文详细介绍了一个基于HTML5的便签应用开发过程,重点讲解了如何利用localStorage实现数据持久化存储,以及如何实现流畅的拖拽交互体验。该应用具有创建、编辑、删除便签功能,自动保存用户内容,并支持多便签管理。文章将深入解析核心实现代码,并提供完整的开发案例。

成品显示

文章最后提供完整的代码,复制粘贴后保存为html文件,可以直接打开如下图,在网页上创建便签,随时记录,修改,删除,关闭网页,不删除便签。
在这里插入图片描述

核心功能与实现语法

1. 本地存储管理

使用localStorage实现便签数据的持久化存储:

note.operate = {
    // 存储便签数据
    set: function (id, content) {
        var notes = this.getNotes();
        if (notes[id]) {
            Object.assign(notes[id], content);
        } else {
            notes[id] = content;
        }
        localStorage.setItem(this.keyItem, JSON.stringify(notes));
    },
    
    // 获取便签数据
    getNotes: function () {
        return JSON.parse(localStorage.getItem(this.keyItem) || '{}');
    },
    
    // 删除便签
    remove: function (id) {
        var notes = this.getNotes();
        delete notes[id];
        localStorage.setItem(this.keyItem, JSON.stringify(notes));
    }
};

2. 拖拽功能实现

精准的拖拽位置计算和层级管理:

// 标题栏拖拽事件
titleBar.addEventListener('mousedown', function(e) {
    movedNote = note;
    var rect = note.getBoundingClientRect();
    startX = e.clientX - rect.left;
    startY = e.clientY - rect.top;
    
    // 提升当前便签的z-index
    maxZindex++;
    note.style.zIndex = maxZindex;
});

// 全局鼠标移动事件
document.addEventListener('mousemove', function(e) {
    if (!movedNote) return;
    movedNote.style.left = (e.clientX - startX) + 'px';
    movedNote.style.top = (e.clientY - startY) + 'px';
});

3. 自动保存机制

使用防抖技术优化内容自动保存:

// 编辑区域事件
var inputTimer;
edit.addEventListener('input', function() {
    var content = edit.innerHTML;
    clearTimeout(inputTimer);
    inputTimer = setTimeout(function() {
        var time = Date.now();
        operate.set(that.note.id, {
            content: content,
            updateTime: time
        });
        that.updateTime(time);
    }, 500); // 500ms防抖延迟
});

4. 时间格式化处理

时间显示格式:

dateFormat: function (ms) {
    var d = new Date(ms);
    var pad = function (s) {
        return s.toString().padStart(2, '0');
    }
    return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} 
            ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

完整代码

<!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;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
            min-height: 100vh;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        
        .header {
            text-align: center;
            margin-bottom: 30px;
            color: white;
            width: 100%;
            max-width: 1200px;
        }
        
        .header h1 {
            font-size: 3rem;
            margin-bottom: 10px;
            text-shadow: 0 2px 10px rgba(0,0,0,0.3);
        }
        
        .header p {
            font-size: 1.2rem;
            opacity: 0.9;
            max-width: 600px;
            margin: 0 auto;
        }
        
        #u-btn-create {
            background: #ff9a00;
            color: white;
            border: none;
            padding: 15px 35px;
            font-size: 1.2rem;
            border-radius: 50px;
            cursor: pointer;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            transition: all 0.3s ease;
            margin-top: 20px;
            font-weight: bold;
            letter-spacing: 1px;
        }
        
        #u-btn-create:hover {
            background: #ff7b00;
            transform: translateY(-3px);
            box-shadow: 0 6px 20px rgba(0,0,0,0.25);
        }
        
        .m-note {
            position: absolute;
            width: 300px;
            background: #fff9c4;
            border-radius: 8px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.15);
            overflow: hidden;
            transition: box-shadow 0.3s ease;
            display: flex;
            flex-direction: column;
            min-height: 320px;
        }
        
        .m-note:hover {
            box-shadow: 0 15px 40px rgba(0,0,0,0.2);
        }
        
        .u-title {
            background: #ffd54f;
            color: #333;
            padding: 12px 20px;
            font-weight: bold;
            font-size: 1.1rem;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .u-close {
            cursor: pointer;
            font-style: normal;
            font-size: 1.5rem;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            transition: all 0.2s ease;
        }
        
        .u-close:hover {
            background: rgba(0,0,0,0.1);
            transform: scale(1.1);
        }
        
        .u-edit {
            flex: 1;
            padding: 20px;
            font-size: 1rem;
            line-height: 1.5;
            outline: none;
            min-height: 200px;
            max-height: 300px;
            overflow-y: auto;
            background: #fffde7;
        }
        
        .u-timeUpdate {
            background: rgba(0,0,0,0.05);
            padding: 10px 20px;
            font-size: 0.85rem;
            color: #666;
        }
        
        .time {
            font-weight: 600;
            color: #333;
        }
        
        .features {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
            gap: 20px;
            max-width: 1000px;
            margin: 40px auto;
        }
        
        .feature-card {
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(10px);
            border-radius: 15px;
            padding: 25px;
            width: 280px;
            color: white;
            box-shadow: 0 8px 20px rgba(0,0,0,0.1);
        }
        
        .feature-card h3 {
            font-size: 1.4rem;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
        }
        
        .feature-card h3 i {
            margin-right: 10px;
            font-size: 1.6rem;
        }
        
        .feature-card p {
            line-height: 1.6;
            opacity: 0.9;
        }
        
        @media (max-width: 768px) {
            .header h1 {
                font-size: 2.2rem;
            }
            
            .features {
                flex-direction: column;
                align-items: center;
            }
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>📝 智能便签应用</h1>
        <p>随时随地记录您的想法、待办事项和重要信息</p>
        <button id="u-btn-create">+ 新建便签</button>
    </div>
    
    <div class="features">
        <div class="feature-card">
            <h3><i>📌</i> 拖拽移动</h3>
            <p>点击标题栏可以拖拽便签到任意位置,方便您整理工作区</p>
        </div>
        <div class="feature-card">
            <h3><i>🔄</i> 自动保存</h3>
            <p>内容编辑后会自动保存到本地存储,刷新页面也不会丢失</p>
        </div>
        <div class="feature-card">
            <h3><i>⏱️</i> 时间记录</h3>
            <p>每次更新便签内容都会记录时间,帮助您追踪进度</p>
        </div>
    </div>

    <script>
        var note = {
            util: {}, //存放基础工具的方法
            operate: {} //存放便签相关的操作
        };

        note.util = {
            $: function (value, node) {
                return (node || document).querySelector(value);
            },
            dateFormat: function (ms) {
                /*格式化时间*/
                var d = new Date(ms);
                var pad = function (s) {
                    if (s.toString().length === 1) {
                        s = '0' + s;
                    }
                    return s;
                }
                var year = d.getFullYear();
                var month = d.getMonth() + 1;
                var date = d.getDate();

                var hour = d.getHours();
                var minute = d.getMinutes();
                var second = d.getSeconds();

                return year + '-' + pad(month) + '-' + pad(date) + ' ' + pad(hour) + ':' + pad(minute) + ':' + pad(second);
            }
        };

        note.operate = {
            /*本地存储的关键字*/
            keyItem: 'stickyNote',

            /*根据便签的id来获取note*/
            get: function (id) {
                var notes = this.getNotes();
                return notes[id] || {};
            },

            /*设置一个note的id及其内容*/
            set: function (id, content) {
                var notes = this.getNotes();
                if (notes[id]) {
                    Object.assign(notes[id], content);
                } else {
                    notes[id] = content;
                }
                localStorage.setItem(this.keyItem, JSON.stringify(notes));
                console.log('saved note: id: ' + id + ' content: ' + JSON.stringify(notes[id]));
            },

            /*根据id删除对应的便签*/
            remove: function (id) {
                var notes = this.getNotes();
                delete notes[id];
                localStorage.setItem(this.keyItem, JSON.stringify(notes));
            },

            /*获取localStorage中所有的note*/
            getNotes: function () {
                return JSON.parse(localStorage.getItem(this.keyItem) || '{}');
            }
        };

        /*创建note*/
        (function (util, operate) {
            var $ = util.$;
            var movedNote = null;
            var startX;
            var startY;
            var maxZindex = 0;

            var noteTemplate = `
                <div class="u-title">便签 <i class="u-close">×</i></div>
                <div class="u-edit" contenteditable="true"></div>
                <div class="u-timeUpdate">
                    <span>更新时间:</span>
                    <span class="time"></span>
                </div>
            `;

            function Note(options) {
                var note = document.createElement('div');
                note.className = 'm-note';
                note.id = options.id || 'm-note-' + Date.now();
                note.innerHTML = noteTemplate;
                $('.u-edit', note).innerHTML = options.content || '';
                note.style.left = (options.left || Math.round(Math.random() * (window.innerWidth - 320))) + 'px';
                note.style.top = (options.top || Math.round(Math.random() * (window.innerHeight - 320))) + 'px';
                note.style.zIndex = options.zIndex || maxZindex++;
                document.body.appendChild(note);
                this.note = note;
                this.updateTime(options.updateTime);
                this.addEvent();
            }

            /*时间更新方法*/
            Note.prototype.updateTime = function (ms) {
                var ts = $('.time', this.note);
                ms = ms || Date.now();
                ts.innerHTML = util.dateFormat(ms);
                this.updateTimeInMs = ms;
            }

            /*便签保存*/
            Note.prototype.save = function () {
                operate.set(this.note.id, {
                    left: this.note.offsetLeft,
                    top: this.note.offsetTop,
                    zIndex: parseInt(this.note.style.zIndex),
                    content: $('.u-edit', this.note).innerHTML,
                    updateTime: this.updateTimeInMs
                });
                console.log('note saved');
            }

            /*删除此便签*/
            Note.prototype.close = function (e) {
                console.log('close note');
                document.body.removeChild(this.note);
            }

            Note.prototype.addEvent = function () {
                var that = this;
                var note = this.note;
                var titleBar = $('.u-title', note);
                var edit = $('.u-edit', note);
                var closeBtn = $('.u-close', note);

                // 标题栏拖拽事件
                titleBar.addEventListener('mousedown', function(e) {
                    if (e.target === closeBtn) return; // 排除关闭按钮
                    
                    movedNote = note;
                    var rect = note.getBoundingClientRect();
                    startX = e.clientX - rect.left;
                    startY = e.clientY - rect.top;
                    
                    // 提升当前便签的z-index
                    maxZindex++;
                    note.style.zIndex = maxZindex;
                    operate.set(note.id, {
                        zIndex: maxZindex
                    });
                });

                // 编辑区域事件
                var inputTimer;
                edit.addEventListener('input', function() {
                    var content = edit.innerHTML;
                    clearTimeout(inputTimer);
                    inputTimer = setTimeout(function() {
                        var time = Date.now();
                        operate.set(that.note.id, {
                            content: content,
                            updateTime: time
                        });
                        that.updateTime(time);
                    }, 500);
                });

                // 关闭按钮事件
                closeBtn.addEventListener('click', function(e) {
                    operate.remove(that.note.id);
                    that.close();
                });
            }


            /*按钮的监听事件*/
            document.addEventListener('DOMContentLoaded', function (e) {
                $('#u-btn-create').addEventListener('click', function (e) {
                    new Note({});
                });

                /*全局鼠标移动事件*/
                function mousemoveHandler(e) {
                    if (!movedNote) return;
                    
                    movedNote.style.left = (e.clientX - startX) + 'px';
                    movedNote.style.top = (e.clientY - startY) + 'px';
                }

                function mouseupHandler(e) {
                    if (!movedNote) return;
                    
                    operate.set(movedNote.id, {
                        left: movedNote.offsetLeft,
                        top: movedNote.offsetTop
                    });
                    
                    movedNote = null;
                }
                
                document.addEventListener('mousemove', mousemoveHandler);
                document.addEventListener('mouseup', mouseupHandler);

                /*初始化*/
                var notes = operate.getNotes();
                Object.keys(notes).forEach(function (id) {
                    var options = notes[id];
                    if (options.zIndex > maxZindex) {
                        maxZindex = options.zIndex;
                    }
                    new Note(Object.assign(options, {
                        id: id
                    }));
                });
                
                // 确保maxZindex至少为1
                maxZindex = Math.max(maxZindex, 1);
            });
        })(note.util, note.operate);
    </script>
</body>
</html>

网站公告

今日签到

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