从 0 到 1 实现 LocalStorage 待办清单:CSS 进阶 + 前端工程化思想实践

在前端开发中,LocalStorage 是浏览器提供的核心本地存储方案,而 CSS 布局与工程化编码则是提升开发效率和代码质量的关键。本文将结合实际案例,带大家从零实现一个可持久化的待办清单,同时拆解 CSS 继承 / 弹性布局的核心用法,以及前端函数式封装的工程化思想。

一、核心知识点预热:先搞懂这 3 个关键技术

在动手写代码前,我们先梳理案例中涉及的核心技术点,帮大家打通知识盲区:

1. LocalStorage:浏览器的「永久储物柜」

  • 存储位置:浏览器本地,与服务器无关

  • 存储特性:永久存储(除非手动清除或代码删除),页面刷新 / 关闭后数据不丢失

  • 存储规则 :仅支持 key-value 键值对,且值必须是字符串(对象 / 数组需用 JSON.stringify() 序列化)

  • 核心 API

    javascript

    运行

    javascript 复制代码
    // 存数据
    localStorage.setItem('key', JSON.stringify(数据));
    // 取数据(需反序列化)
    JSON.parse(localStorage.getItem('key'));
    // 删数据(单个/全部)
    localStorage.removeItem('key');
    localStorage.clear();

2. CSS 关键特性:这些细节能少写 80% 冗余代码

(1)继承性:不是所有属性都能「子承父业」

  • 可继承属性font-sizecolortext-align 等文本相关属性(子元素自动继承父元素样式)

  • 不可继承属性backgroundwidthheightborder 等布局 / 盒模型相关属性(需手动设置)

  • 实用技巧 :用 inherit 强制继承父属性,比如让子元素高度跟随父元素:

    css

    css 复制代码
    .child {
      height: inherit; /* 继承父元素 height */
    }

(2)outline 与 overflow:细节优化神器

  • outline :元素轮廓,类似 border 但不占据盒模型空间(适合表单聚焦样式)
  • overflow :控制子元素超出父元素时的表现(hidden 隐藏溢出、auto 自动滚动)

(3)Flex 布局:快速实现居中与对齐

Flex 是现代布局方案,核心是「格式化上下文」,只需 3 行代码实现元素居中:

css

css 复制代码
.parent {
  display: flex; /* 开启 Flex 布局 */
  justify-content: center; /* 主轴(水平)居中 */
  align-items: center; /* 交叉轴(垂直)居中 */
}

3. 前端工程化:拒绝流程式代码,拥抱函数封装

当代码超过 10 行,就该考虑封装函数!核心优势:

  • 复用性:同一逻辑多处调用,减少冗余
  • 可维护性:隐藏实现细节,修改时只需改函数内部
  • 可读性:函数名即功能,代码逻辑更清晰

二、实战:实现 LocalStorage 待办清单

1. 项目结构

plaintext

bash 复制代码
todo-list/
├── common.css  # 样式文件
└── index.html  # 结构+逻辑文件

2. 样式文件:common.css

css

css 复制代码
/* 基础重置:统一盒模型 */
html {
  box-sizing: border-box;
  min-height: 100vh; /* 占满视口高度 */
  display: flex; /* 页面整体居中 */
  justify-content: center;
  align-items: center;
  text-align: center; /* 文本居中 */
  background-color: #f5f5f5;
}

*,
*::before,
*::after {
  box-sizing: inherit; /* 继承盒模型 */
}

/* 容器样式 */
.wrapper {
  padding: 20px;
  max-width: 350px;
  background-color: rgba(255, 255, 255, 0.95);
  box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); /* 外发光效果 */
  border-radius: 8px;
}

h2 {
  margin: 0 0 20px;
  font-weight: 200;
  color: #333;
}

/* 待办列表样式 */
.plates {
  margin: 0;
  padding: 0;
  text-align: left;
  list-style: none;
}

.plates li {
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  padding: 10px 0;
  font-weight: 100;
  display: flex; /* 复选框与文本同行 */
  align-items: center;
}

.plates label {
  flex: 1; /* 文本占满剩余空间 */
  cursor: pointer; /* 鼠标悬浮变指针 */
  color: #444;
}

/* 自定义复选框样式 */
.plates input {
  display: none; /* 隐藏原生复选框 */
}

.plates input + label:before {
  content: "⬜️";
  margin-right: 10px;
  font-size: 18px;
}

.plates input:checked + label:before {
  content: "✅"; /* 选中时切换图标 */
}

/* 表单样式 */
.add-items {
  margin-top: 20px;
  display: flex;
  gap: 10px; /* 输入框与按钮间距 */
}

.add-items input[type="text"] {
  flex: 1;
  padding: 10px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  outline: 3px solid transparent;
  transition: outline 0.3s;
}

.add-items input[type="text"]:focus {
  outline: 3px solid rgba(14, 14, 211, 0.8); /* 聚焦高亮 */
}

.add-items input[type="submit"] {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  background-color: rgba(14, 14, 211, 0.8);
  color: white;
  cursor: pointer;
  transition: background-color 0.3s;
}

.add-items input[type="submit"]:hover {
  background-color: rgba(14, 14, 211, 1);
}

3. 核心文件:index.html

html

预览

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>LocalStorage 待办清单</title>
  <link rel="stylesheet" href="./common.css">
</head>
<body>
  <div class="wrapper">
    <h2>LOCAL TAPAS</h2>
    <ul class="plates">
      <li>Loading Tapas...</li>
    </ul>
    <form class="add-items">
      <input 
        type="text" 
        placeholder="输入待办事项" 
        required
        name="item"
      >
      <input type="submit" value="+ 添加事项">
    </form>
  </div>

  <script>
    // 1. 获取 DOM 元素
    const addItemsForm = document.querySelector('.add-items');
    const itemsList = document.querySelector('.plates');
    // 2. 从 LocalStorage 取数据(无则初始化空数组)
    const items = JSON.parse(localStorage.getItem('todos')) || [];

    /**
     * 渲染待办列表
     * @param {Array} plates - 待办数组
     * @param {Element} platesList - 列表 DOM 元素
     */
    function populateList(plates = [], platesList) {
      // 数组 map 生成 DOM 字符串,join 拼接(避免逗号分隔)
      platesList.innerHTML = plates.map((plate, index) => `
        <li>
          <input 
            type="checkbox" 
            data-index="${index}" 
            id="item${index}"
            ${plate.done ? 'checked' : ''}
          />
          <label for="item${index}">${plate.text}</label>
        </li>
      `).join('');
    }

    /**
     * 添加待办事项
     * @param {Event} event - 表单提交事件
     */
    function addItem(event) {
      event.preventDefault(); // 阻止表单默认提交行为
      // 获取输入框值(去空格)
      const text = this.querySelector('[name=item]').value.trim();
      if (!text) return; // 空值不添加

      // 新增待办对象
      const newItem = {
        text,
        done: false // 默认未完成
      };

      items.push(newItem);
      // 存入 LocalStorage(序列化数组)
      localStorage.setItem('todos', JSON.stringify(items));
      // 重新渲染列表
      populateList(items, itemsList);
      this.reset(); // 重置表单
    }

    /**
     * 切换待办完成状态
     * @param {Event} event - 点击事件
     */
    function toggleDone(event) {
      const target = event.target;
      // 只处理复选框的点击
      if (target.tagName !== 'INPUT') return;

      // 获取数据索引(从 data-index 属性)
      const index = target.dataset.index;
      // 切换完成状态
      items[index].done = !items[index].done;
      // 更新 LocalStorage
      localStorage.setItem('todos', JSON.stringify(items));
      // 重新渲染
      populateList(items, itemsList);
    }

    // 3. 绑定事件监听
    addItemsForm.addEventListener('submit', addItem);
    itemsList.addEventListener('click', toggleDone);

    // 4. 页面加载时渲染列表
    populateList(items, itemsList);
  </script>
</body>
</html>

三、关键技术拆解:为什么这么写?

1. LocalStorage 持久化逻辑

  • 初始化 :页面加载时从 localStorage 读取 todos 数据,若不存在则初始化为空数组
  • 新增待办 :添加后立即用 JSON.stringify() 序列化数组,存入 localStorage
  • 状态切换 :修改待办完成状态后,同步更新 localStorage,确保刷新后状态不丢失

2. CSS 进阶技巧

  • 盒模型继承 :通过 box-sizing: inherit 让所有元素(包括伪元素)继承 border-box,避免计算宽度时的麻烦
  • Flex 布局妙用:页面整体居中、待办项复选框与文本对齐、表单输入框与按钮同行分布,都用 Flex 实现,简洁高效
  • 自定义复选框 :隐藏原生复选框,用 :before 伪元素实现自定义图标,提升视觉体验
  • 聚焦状态优化:输入框聚焦时添加蓝色轮廓,提升交互反馈

3. 工程化编码思想

(1)函数式封装:拒绝流程式代码

  • 把「渲染列表」「添加待办」「切换状态」拆分为 3 个独立函数,每个函数只做一件事
  • 函数参数化:populateList 接收待办数组和列表 DOM 元素,增强复用性
  • 避免全局变量污染:核心数据 items 通过函数参数传递,而非依赖全局变量

(2)细节优化:提升用户体验

  • 输入框去空格:用 trim() 避免添加空待办
  • 空值判断:输入为空时不添加待办
  • 表单重置:添加后清空输入框,方便连续输入
  • 事件委托:给列表父元素绑定点击事件,而非给每个复选框绑定,提升性能(尤其待办较多时)

四、扩展与优化方向

  1. 添加删除功能 :给每个待办项增加删除按钮,点击时删除对应项并更新 localStorage
  2. 批量操作:实现「全选 / 全不选」「清空已完成」功能
  3. 数据持久化优化 :封装 localStorage 操作工具函数,避免重复写 JSON.stringifyJSON.parse
  4. 样式升级:添加动画效果(如待办项添加 / 删除时的过渡动画)
  5. 响应式优化:适配移动端,优化小屏幕下的布局

五、总结

本文通过一个简单的待办清单案例,串联了 LocalStorage 本地存储、CSS 进阶特性和前端工程化编码思想。核心要点:

  • LocalStorage 是前端持久化存储的基础,关键是掌握「序列化 / 反序列化」技巧
  • CSS 不仅是样式,合理运用继承、Flex 布局和伪元素,能大幅提升开发效率和视觉体验
  • 工程化编码的核心是「拆分与封装」,让代码更易维护、可复用

这个案例虽然简单,但包含了前端开发的核心思想,适合新手入门练习,也能帮助有经验的开发者巩固基础。动手试试扩展功能,让它变得更强大吧!

相关推荐
WaterFly4 小时前
浅谈Tree Shaking
前端工程化
凌波粒5 小时前
CSS基础详解(1)
前端·css
yqcoder20 小时前
在 scss 中,&>div 作用
前端·css·scss
小马哥编程20 小时前
这个variables.scss文件中$menuText:#bfcbd9;:export {menuText: $menuText; }的语法符合要求吗
前端·css·scss
爆浆麻花21 小时前
为什么有些人边框不用border属性
前端·css
www_stdio1 天前
用 localStorage 打造本地待办清单:一个轻量级的前端实践
javascript·css·json
光影少年1 天前
css影响性能及优化方案都有哪些
前端·css
stormsha1 天前
CSS 样式美学从基础语法到界面精筑的实战宝典
前端·css·postcss·设计语言
yqcoder1 天前
css 中,backdrop-filter: blur(10px) 作用
前端·css