LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

使用 HTML + JavaScript 实现表单自动保存(附完整代码)

admin
2026年1月5日 15:14 本文热度 400
用户在填写表单时,经常会遇到意外关闭页面或浏览器崩溃的情况,导致长时间的输入成果丢失。通过实现表单自动保存功能,可以在用户输入时实时保存数据到本地存储,当用户意外离开后重新进入页面时,能够恢复之前填写的内容,大大提升用户体验。这种机制特别适用于注册表单、问卷调查、订单填写等需要用户投入时间的场景。本文将介绍如何使用 HTML、CSS 和 JavaScript 实现表单自动保存功能。

效果演示

当用户在表单中进行输入操作时,系统会在用户停止输入 1 秒后自动将当前填写的数据保存到浏览器的本地存储中。页面右上角会显示一个"已自动保存"的提示信息,提醒用户数据已保存。当用户刷新页面或重新访问该页面时,之前填写的数据会自动恢复到相应的表单控件中。即使用户意外关闭浏览器后再次打开,数据依然存在。用户可以通过提交按钮提交表单,或使用清空按钮清除表单内容和本地存储。

页面结构

页面主要包括以下几个区域:

表单主体区域

包含各种类型的表单控件,如文本输入框、日期选择器、单选按钮组、复选框组、下拉选择器和文本域。
<div class="form-group">  <label for="name">姓名</label>  <input type="text" id="name" name="name" placeholder="请输入您的姓名"></div><div class="form-group">  <label for="birthdate">出生日期</label>  <input type="date" id="birthdate" name="birthdate"></div><!-- 更多表单元素... -->

操作按钮区域

包含提交表单和清空表单的按钮。
<div class="buttons">  <button type="submit" class="btn-primary">提交表单</button>  <button type="button" class="btn-secondary" onclick="clearForm()">清空表单</button></div>

提示信息区域

位于页面右上角,用于显示自动保存状态的提示信息。
<div id="saveIndicator" class="save-indicator">已自动保存</div>

核心功能实现

自动保存类初始化

创建 AutoSaveForm 类,用于管理表单的自动保存功能。在构造函数中初始化必要的 DOM 元素引用,并在 init 方法中设置事件监听器。主要监听 input 和 change 事件来检测用户输入,同时监听页面可见性变化以在用户切换标签页时保存数据。
class AutoSaveForm {  constructor(formId, storageKey = 'autoSaveForm') {    this.form = document.getElementById(formId);    this.storageKey = storageKey;    this.saveIndicator = document.getElementById('saveIndicator');    this.status = document.getElementById('status');    this.saveTimeout = null;    this.init();  }
  init() {    this.loadSavedData();    this.form.addEventListener('input', (e) => this.handleInput(e));    this.form.addEventListener('change', (e) => this.handleInput(e));    this.form.addEventListener('submit', (e) => this.handleSubmit(e));    document.addEventListener('visibilitychange', () => {      if (document.visibilityState === 'hidden'this.saveData();    });  }}

输入事件处理与延迟保存

当用户输入时,需要避免频繁保存造成性能问题。通过 handleInput 方法实现防抖机制,每次输入后设置 1 秒的延迟,如果在延迟期间又有新的输入,则清除之前的定时器并重新设置,确保只在用户停止输入后才执行保存操作。
handleInput(e) {  if (this.saveTimeoutclearTimeout(this.saveTimeout);  this.saveTimeout = setTimeout(() => this.saveData(), 1000);}

数据收集与本地存储

saveData 方法负责收集表单中的所有数据并保存到 localStorage。对于复选框组,需要特殊处理以正确收集所有选中的值。同时处理未选中的复选框,确保它们的值也被正确记录。
saveData() {  const data = {};  const formData = new FormData(this.form);  for (let [key, value] of formData.entries()) {    const elements = this.form.querySelectorAll(`input[name="${key}"][type="checkbox"]`);    if (elements.length > 1) {      if (data[key]) {        if (!Array.isArray(data[key])) data[key] = [data[key]];        data[key].push(value);      } else {        data[key] = [value];      }    } else {      data[key] = value;    }  }  this.form.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {    if (!formData.has(checkbox.name)) {      const groupCheckboxes = this.form.querySelectorAll(`input[type="checkbox"][name="${checkbox.name}"]`);      if (groupCheckboxes.length > 1) {        if (!data[checkbox.name]) data[checkbox.name] = [];      } else {        data[checkbox.name] = false;      }    }  });  localStorage.setItem(this.storageKey, JSON.stringify(data));  this.showSaveIndicator();}

数据恢复与状态显示

loadSavedData 方法从本地存储中读取之前保存的数据,并将其恢复到相应的表单控件中。针对不同类型的表单元素(文本框、单选框、复选框)使用不同的恢复逻辑。showSaveIndicator 方法控制保存状态提示信息的显示和隐藏。
loadSavedData() {  const savedData = localStorage.getItem(this.storageKey);  if (savedData) {    const data = JSON.parse(savedData);    Object.keys(data).forEach(key => {      const elements = this.form.elements[key];      if (elements) {        if (elements.type === 'checkbox' || elements[0]?.type === 'checkbox') {          this.setCheckboxValues(elements, data[key]);        } else if (elements.type === 'radio' || elements[0]?.type === 'radio') {          this.setRadioValues(elements, data[key]);        } else if (Array.isArray(data[key]) && elements.length) {          Array.from(elements).forEach(el => el.checked = data[key].includes(el.value));        } else {          elements.value = data[key];        }      }    });  }}

完整代码

git地址:https://gitee.com/ironpro/hjdemo/blob/master/form-autosave/index.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>      * {          margin0;          padding0;          box-sizing: border-box;      }      body {          background#f5f5f5;          padding20px;          min-height100vh;          color#333;      }      .container {          background: white;          padding20px;          width800px;          margin0 auto;          box-shadow0 2px 10px rgba(0,0,0,0.05);      }      h1 {          color#2c3e50;          font-size22px;          margin-bottom25px;          text-align: center;          border-bottom1px solid #eee;          padding-bottom15px;      }      .form-group {          margin-bottom20px;          display: flex;          flex-direction: row;          align-items: flex-start;      }      .form-group.checkbox-single {          align-items: center;      }      label {          display: inline-block;          margin-bottom0;          color#374151;          font-size14px;          font-weight500;          width120px;          flex-shrink0;      }      .form-group.checkbox-single label {          width: auto;          margin-left8px;      }      input[type="text"]input[type="email"]input[type="number"]input[type="date"]textareaselect {          widthcalc(100% - 130px);          padding10px 14px;          border1px solid #d1d5db;          font-size14px;          transition: border-color 0.2s;          background: white;      }      .form-group.checkbox-single input[type="checkbox"] {          width: auto;      }      input[type="text"]:focusinput[type="email"]:focusinput[type="number"]:focusinput[type="date"]:focustextarea:focusselect:focus {          outline: none;          border-color#3b82f6;          box-shadow0 0 0 2px rgba(59130246, .1);      }      textarea {          resize: vertical;          min-height100px;      }      .radio-group.checkbox-group.checkbox-grid {          display: flex;          flex-wrap: wrap;          gap16px;          widthcalc(100% - 130px);      }      .radio-item.checkbox-item {          display: flex;          align-items: center;          gap8px;          cursor: pointer;          padding6px 0;          flex-shrink0;      }      input[type="radio"]input[type="checkbox"] {          width16px;          height16px;          cursor: pointer;          accent-color#3b82f6;      }      .save-indicator {          position: fixed;          top16px;          right16px;          background#10b981;          color: white;          padding8px 16px;          border-radius4px;          font-size14px;          opacity0;          transformtranslateY(-10px);          transition: all 0.2s ease;          pointer-events: none;          box-shadow0 2px 4px rgba(000, .1);      }      .save-indicator.show {          opacity1;          transformtranslateY(0);      }      .buttons {          display: flex;          gap14px;          margin-top24px;      }      button {          padding10px 20px;          border1px solid transparent;          font-size14px;          cursor: pointer;          transition: all 0.2s;          font-weight500;      }      .btn-primary {          background#3b82f6;          color: white;      }      .btn-primary:hover {          background#2563eb;      }      .btn-secondary {          background: white;          color#374151;          border1px solid #d1d5db;      }      .btn-secondary:hover {          background#f3f4f6;      }  </style></head><body><div class="container">  <h1>表单自动保存</h1>  <form id="autoSaveForm">    <div class="form-group">      <label for="name">姓名</label>      <input type="text" id="name" name="name" placeholder="请输入您的姓名">    </div>    <div class="form-group">      <label for="birthdate">出生日期</label>      <input type="date" id="birthdate" name="birthdate">    </div>    <div class="form-group">      <label>性别</label>      <div class="radio-group">        <div class="radio-item">          <input type="radio" id="male" name="gender" value="male">          <label for="male"></label>        </div>        <div class="radio-item">          <input type="radio" id="female" name="gender" value="female">          <label for="female"></label>        </div>      </div>    </div>    <div class="form-group">      <label>兴趣爱好</label>      <div class="checkbox-grid">        <div class="checkbox-item">          <input type="checkbox" id="reading" name="hobbies" value="reading">          <label for="reading">阅读</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="sports" name="hobbies" value="sports">          <label for="sports">运动</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="music" name="hobbies" value="music">          <label for="music">音乐</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="travel" name="hobbies" value="travel">          <label for="travel">旅行</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="cooking" name="hobbies" value="cooking">          <label for="cooking">烹饪</label>        </div>      </div>    </div>    <div class="form-group">      <label>技能水平</label>      <div class="radio-group">        <div class="radio-item">          <input type="radio" id="beginner" name="skill" value="beginner">          <label for="beginner">初学者</label>        </div>        <div class="radio-item">          <input type="radio" id="intermediate" name="skill" value="intermediate">          <label for="intermediate">中级</label>        </div>        <div class="radio-item">          <input type="radio" id="advanced" name="skill" value="advanced">          <label for="advanced">高级</label>        </div>      </div>    </div>    <div class="form-group">      <label for="city">城市</label>      <select id="city" name="city">        <option value="">请选择城市</option>        <option value="beijing">北京</option>        <option value="shanghai">上海</option>        <option value="guangzhou">广州</option>        <option value="shenzhen">深圳</option>        <option value="hangzhou">杭州</option>        <option value="chengdu">成都</option>      </select>    </div>    <div class="form-group">      <label>通知设置</label>      <div class="checkbox-group">        <div class="checkbox-item">          <input type="checkbox" id="emailNotify" name="notifications" value="email">          <label for="emailNotify">邮件通知</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="smsNotify" name="notifications" value="sms">          <label for="smsNotify">短信通知</label>        </div>        <div class="checkbox-item">          <input type="checkbox" id="pushNotify" name="notifications" value="push">          <label for="pushNotify">推送通知</label>        </div>      </div>    </div>    <div class="form-group">      <label for="message">留言</label>      <textarea id="message" name="message" placeholder="请输入您的留言"></textarea>    </div>    <div class="form-group checkbox-single">      <input type="checkbox" id="agree" name="agree">      <label for="agree">我已阅读并同意服务条款</label>    </div>    <div class="buttons">      <button type="submit" class="btn-primary">提交表单</button>      <button type="button" class="btn-secondary" onclick="clearForm()">清空表单</button>    </div>  </form></div>
<div id="saveIndicator" class="save-indicator">已自动保存</div>
<script>  class AutoSaveForm {    constructor(formId, storageKey = 'autoSaveForm') {      this.form = document.getElementById(formId);      this.storageKey = storageKey;      this.saveIndicator = document.getElementById('saveIndicator');      this.status = document.getElementById('status');      this.saveTimeout = null;      this.init();    }
    init() {      this.loadSavedData();      this.form.addEventListener('input'(e) => this.handleInput(e));      this.form.addEventListener('change'(e) => this.handleInput(e));      this.form.addEventListener('submit'(e) => this.handleSubmit(e));      document.addEventListener('visibilitychange'() => {        if (document.visibilityState === 'hidden'this.saveData();      });    }
    handleInput(e) {      if (this.saveTimeoutclearTimeout(this.saveTimeout);      this.saveTimeout = setTimeout(() => this.saveData(), 1000);    }
    saveData() {      const data = {};      const formData = new FormData(this.form);      for (let [key, value] of formData.entries()) {        const elements = this.form.querySelectorAll(`input[name="${key}"][type="checkbox"]`);        if (elements.length > 1) {          if (data[key]) {            if (!Array.isArray(data[key])) data[key] = [data[key]];            data[key].push(value);          } else {            data[key] = [value];          }        } else {          data[key] = value;        }      }      this.form.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {        if (!formData.has(checkbox.name)) {          const groupCheckboxes = this.form.querySelectorAll(`input[type="checkbox"][name="${checkbox.name}"]`);          if (groupCheckboxes.length > 1) {            if (!data[checkbox.name]) data[checkbox.name] = [];          } else {            data[checkbox.name] = false;          }        }      });      localStorage.setItem(this.storageKeyJSON.stringify(data));      this.showSaveIndicator();    }
    loadSavedData() {      const savedData = localStorage.getItem(this.storageKey);      if (savedData) {        const data = JSON.parse(savedData);        Object.keys(data).forEach(key => {          const elements = this.form.elements[key];          if (elements) {            if (elements.type === 'checkbox' || elements[0]?.type === 'checkbox') {              this.setCheckboxValues(elements, data[key]);            } else if (elements.type === 'radio' || elements[0]?.type === 'radio') {              this.setRadioValues(elements, data[key]);            } else if (Array.isArray(data[key]) && elements.length) {              Array.from(elements).forEach(el => el.checked = data[key].includes(el.value));            } else {              elements.value = data[key];            }          }        });      }    }
    setCheckboxValues(elements, savedValue) {      if (elements.length) {        Array.from(elements).forEach(checkbox => {          if (Array.isArray(savedValue)) checkbox.checked = savedValue.includes(checkbox.value);          else checkbox.checked = savedValue === true;        });      } else {        elements.checked = savedValue === true || elements.value === savedValue;      }    }
    setRadioValues(elements, savedValue) {      if (elements.lengthArray.from(elements).forEach(radio => radio.checked = radio.value === savedValue);      else elements.checked = elements.value === savedValue;    }
    showSaveIndicator() {      this.saveIndicator.classList.add('show');      setTimeout(() => this.saveIndicator.classList.remove('show'), 2000);    }
    handleSubmit(e) {      e.preventDefault();      this.saveData();      setTimeout(() => {        if (confirm('是否清空已保存的数据?')) {          this.clearSavedData();          this.form.reset();        }      }, 1000);    }
    clearSavedData() {      localStorage.removeItem(this.storageKey);    }  }
  const autoSaveForm = new AutoSaveForm('autoSaveForm');
  function clearForm() {    if (confirm('确定要清空表单吗?')) {      autoSaveForm.clearSavedData();      document.getElementById('autoSaveForm').reset();    }  }</script></body></html>


阅读原文:https://mp.weixin.qq.com/s/Am8HSmh3yo6X3_RBw50E9g


该文章在 2026/1/5 15:58:08 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved