欢迎访问晨星博客!

  • 当前位置: 首页 Web前端 正文

    用原生 dialog 替换自写弹窗:哪些 JS 该删,哪些必须保留

    生成摘要
    AI 生成,仅供参考

    如果你的 WordPress 主题或插件里还保留着一套 jQuery 弹窗,改造时最容易犯的错误,是把旧代码原样搬到 <dialog> 外面,再额外套一层原生 API。这样虽然换了标签,却没有真正减少维护成本。更合适的做法是先确认浏览器已经接管了哪些模态行为,再逐项删除重复逻辑,只保留业务流程和项目特有的交互。

    将自写弹窗改造成原生 dialog 的代码重构场景

    先把旧弹窗拆成四类代码

    改造已有弹窗前,不要直接搜索并删除所有 modaloverlaykeydown。先按职责给旧代码分类,因为它们并不都属于“弹窗显示隐藏”这一层。

    通常可以分成四组:

    1. 结构与样式:弹窗容器、遮罩层、定位、层级和隐藏状态。
    2. 模态行为:阻止背景操作、把焦点限制在弹窗内、按 Esc 关闭。
    3. 业务控制:点击哪个按钮打开、打开前填充什么内容、提交后执行什么操作。
    4. 项目特有体验:关闭动画、点击遮罩关闭、关闭后读取表单值、不支持原生能力时的替代方案。

    showModal() 主要接管第二组的一部分,同时简化第一组中的遮罩实现,但不会替你完成第三组和第四组。真正的删改取舍,也应以这个边界为准。

    showModal() 替换旧的显示逻辑

    一个已有的确认弹窗,通常可以先收敛成这样的结构:

    <button type="button" class="js-open-dialog">删除这篇文章</button>
    
    <dialog id="delete-dialog" aria-labelledby="delete-dialog-title">
      <h2 id="delete-dialog-title">确认删除</h2>
      <p>删除操作需要你再次确认。</p>
    
      <form method="dialog">
        <button type="submit" value="cancel">取消</button>
        <button type="submit" value="confirm">确认删除</button>
      </form>
    </dialog>

    对应的打开代码只需要负责找到目标弹窗,并在合适的时机调用 showModal()

    const openButton = document.querySelector('.js-open-dialog');
    const dialog = document.querySelector('#delete-dialog');
    
    openButton?.addEventListener('click', () => {
      dialog.showModal();
    });

    这里的关键变化不是把 div 改成了 dialog,而是把原来可能存在的:

    overlay.classList.add('is-visible');
    modal.classList.add('is-open');
    document.body.classList.add('modal-open');

    以及与之对应的隐藏逻辑,改成了原生的:

    dialog.showModal();
    dialog.close();

    调用 showModal() 后,弹窗进入模态状态,弹窗外的页面内容不能像普通页面那样继续接受操作。原生机制也会处理模态对话框的焦点进入和焦点返回等问题,因此大多数项目不再需要自己遍历可聚焦元素、监听 Tab 并手动循环焦点。

    可以直接删除的旧逻辑

    如果这些代码只是为了模拟模态弹窗的基础行为,通常可以删除或停止使用:

    • 专门承载整页遮罩的 .modal-overlay 元素;
    • 通过 z-indexdisplay 控制弹窗出现的整套状态类;
    • tabindex、焦点元素数组和 Tab 键监听模拟焦点收敛的代码;
    • keydown 监听 Esc,再手动调用旧的隐藏函数;
    • 为阻止背景点击而给页面各处添加的临时事件拦截;
    • 打开弹窗时反复记录焦点、关闭时再手动聚焦触发按钮的重复实现。

    不过,“可以删除”有一个前提:这些代码没有同时承担业务功能。例如旧的遮罩层如果还负责点击空白区域关闭、显示加载状态或承载自定义动画,就不能只因为使用了 <dialog> 而全部移除。

    Esc 监听不要急着保留

    模态 dialog 本身已经支持使用 Esc 触发关闭流程。若旧代码只是这样:

    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape' && modal.classList.contains('is-open')) {
        closeModal();
      }
    });

    那么这段监听通常可以删掉。继续保留它,可能造成一次按键触发两套关闭逻辑:原生对话框准备关闭,旧函数又修改类名、移除遮罩,最后留下状态不同步的问题。

    如果业务上确实不能让用户直接按 Esc 退出,例如未保存表单需要二次确认,就不应再用全局键盘监听模拟关闭,而应监听 cancel 事件并明确决定是否阻止默认行为:

    dialog.addEventListener('cancel', (event) => {
      if (dialog.dataset.dirty === 'true') {
        event.preventDefault();
        // 在这里显示“是否放弃修改”的业务提示
      }
    });

    这段代码的作用不是重新实现 Esc 关闭,而是对原生关闭流程增加业务限制。没有特殊需求时,不要为了“保险”而拦截它。

    遮罩层可以删,遮罩样式不一定要删

    原生模态对话框会生成与对话框关联的背景层,可以通过 ::backdrop 设置样式:

    dialog {
      border: 0;
      border-radius: 12px;
      padding: 0;
      max-width: min(90vw, 32rem);
    }
    
    dialog::backdrop {
      background: rgb(0 0 0 / 55%);
    }

    这时,旧 HTML 中用于铺满整个视口的遮罩元素可以移除,原来写在 .modal-overlay 上的背景色也应迁移到 dialog::backdrop

    但下面几类样式仍然可能需要保留:

    • 对话框本身的宽度、内边距和定位;
    • 内容区域的滚动处理;
    • 打开与关闭时的过渡效果;
    • 特殊场景下的视觉层级;
    • 与主题现有设计系统匹配的边框、阴影和圆角。

    不要把 ::backdrop 理解成一个可以放置任意 HTML 内容的容器。它适合控制背景遮罩的视觉效果,不适合替代原先放在遮罩层里的按钮、提示信息或复杂交互。

    关闭后如何拿回表单结果

    弹窗能否打开只是第一步。已有项目通常还会在用户点击“确定”或“取消”后执行不同操作,这部分不会因为改用原生 dialog 自动完成。

    使用 method="dialog" 的表单时,提交按钮的 value 可以作为结果保存下来:

    <dialog id="delete-dialog">
      <form method="dialog">
        <p>这项操作无法撤销。</p>
        <button value="cancel">取消</button>
        <button value="confirm">确认删除</button>
      </form>
    </dialog>

    然后在 close 事件中读取 returnValue

    dialog.addEventListener('close', () => {
      const result = dialog.returnValue;
    
      if (result === 'confirm') {
        removePost();
      }
    });

    这里的 removePost() 只是站点已有的前端后续流程,实际项目中可以替换为更新页面状态、刷新列表或调用已有接口。原生 dialog 不会替你判断业务结果,也不会自动区分“确认关闭”和“用户按 Esc 关闭”。

    如果你需要在 JavaScript 中主动传递结果,也可以使用:

    dialog.close('confirm');

    因此,改造时应重点检查旧代码里的这些位置:

    • 点击确认按钮后执行了什么;
    • 点击取消按钮后是否清理临时数据;
    • 按 Esc 关闭时是否应该被视为取消;
    • 通过关闭动画结束回调触发的业务逻辑;
    • 关闭后是否需要恢复按钮状态或焦点。

    这些属于业务流程,不能因为删除了旧的 closeModal() 就一并删除。

    打开时机仍然必须自己控制

    dialog 不知道自己什么时候该出现,也不知道要显示哪一条内容。尤其在 WordPress 主题或插件中,一个页面可能有多个文章卡片、多个编辑按钮或多个动态生成的弹窗,打开前通常需要写入上下文数据。

    例如,一个弹窗服务于多个按钮:

    <button
      type="button"
      class="js-edit-button"
      data-item-id="42"
      data-item-name="示例内容">
      编辑
    </button>
    
    <dialog id="edit-dialog">
      <form method="dialog" id="edit-form">
        <label>
          名称
          <input name="name" type="text">
        </label>
    
        <button value="cancel">取消</button>
        <button value="save">保存</button>
      </form>
    </dialog>

    打开时仍要读取按钮上的数据,并填充表单:

    const editDialog = document.querySelector('#edit-dialog');
    const editForm = document.querySelector('#edit-form');
    
    document.addEventListener('click', (event) => {
      const button = event.target.closest('.js-edit-button');
    
      if (!button) {
        return;
      }
    
      editForm.elements.name.value = button.dataset.itemName || '';
      editDialog.dataset.itemId = button.dataset.itemId || '';
    
      editDialog.showModal();
    });

    这里保留下来的不是“弹窗框架代码”,而是页面业务需要的上下文准备。把旧的打开函数改成调用 showModal(),不等于可以删除打开前的数据填充、权限判断、按钮禁用或状态初始化。

    动画是原生 API 没有替你完成的部分

    很多自写弹窗会在打开和关闭时添加动画类。迁移到 dialog 后,不能简单地认为 display: nonedisplay: block 的切换就能自然产生过渡。

    可以先保留视觉层面的动画需求,再让 JavaScript 只处理状态切换。例如:

    dialog {
      opacity: 0;
      transform: translateY(12px);
      transition:
        opacity 180ms ease,
        transform 180ms ease;
    }
    
    dialog[open] {
      opacity: 1;
      transform: translateY(0);
    }
    
    dialog::backdrop {
      background: rgb(0 0 0 / 0%);
      transition: background 180ms ease;
    }
    
    dialog[open]::backdrop {
      background: rgb(0 0 0 / 55%);
    }

    不过,关闭动画比打开动画更需要检查。调用 close() 后,open 状态会被移除,元素可能立即回到关闭状态,导致过渡还没完成就消失。若项目非常依赖平滑关闭,可以保留一层很小的动画控制逻辑:先添加退出状态,监听过渡完成,再调用真正的 close()

    这部分代码不应继续承担焦点管理、遮罩点击和键盘处理。它只负责“怎么离场”,职责会比旧的完整 modal 控制器更窄。

    点击遮罩关闭要单独决定

    showModal() 提供了模态状态和背景层,但“点击遮罩是否关闭”仍然是产品交互选择,不应默认把它和原生行为混为一谈。

    如果站点原来的交互允许点击弹窗外部关闭,可以根据点击位置判断事件是否发生在对话框边界之外:

    dialog.addEventListener('click', (event) => {
      const rect = dialog.getBoundingClientRect();
      const clickedOutside =
        event.clientX < rect.left ||
        event.clientX > rect.right ||
        event.clientY < rect.top ||
        event.clientY > rect.bottom;
    
      if (clickedOutside) {
        dialog.close('cancel');
      }
    });

    如果这是确认删除、编辑内容等不应轻易丢失状态的弹窗,则可以不实现这段逻辑,让用户通过明确的取消按钮关闭。迁移时不要为了追求“代码更少”,把原本重要的防误触体验删掉。

    不支持时要准备降级路径

    原生能力减少了现代浏览器中的自写代码,但面向 WordPress 站点时,仍应考虑主题、插件和访问者环境的差异。降级判断至少要放在调用 showModal() 之前:

    const canUseModalDialog =
      typeof HTMLDialogElement !== 'undefined' &&
      typeof dialog.showModal === 'function';
    
    openButton?.addEventListener('click', () => {
      if (canUseModalDialog) {
        dialog.showModal();
        return;
      }
    
      dialog.setAttribute('open', '');
      dialog.classList.add('is-fallback-open');
    });

    降级方案可以继续使用项目原有的显示隐藏类,但要明确它只是备用路径,不要让两套实现同时修改同一组状态。否则在支持原生 dialog 的环境中,旧遮罩、旧键盘监听和原生状态可能互相覆盖。

    如果项目已经有统一的前端能力检测或兼容层,应接入现有机制,而不是在每个弹窗中重复写一份判断。

    原生 dialog 弹窗迁移前后的代码取舍检查

    一份可以照做的删改顺序

    为了避免一次改动过大,可以按下面的顺序处理已有弹窗:

    1. 先替换 HTML 根元素

    将承担对话框内容的容器改成 <dialog>,保留标题、表单和按钮结构。暂时不要删除业务事件。

    1. 把打开和关闭入口接到原生方法

    showModal() 替换旧的显示类,用 close() 替换旧的隐藏类。确认同一个弹窗不会重复调用 showModal()

    1. 迁移遮罩样式

    删除无独立用途的遮罩节点,把背景色迁移到 dialog::backdrop。检查弹窗宽度、滚动和层级样式是否仍然正常。

    1. 删除重复的模态行为

    移除焦点循环、全局 Esc 监听和背景点击拦截。每删一组代码,都要确认它没有额外承担业务功能。

    1. 恢复关闭结果处理

    对确认、取消、Esc 和脚本主动关闭分别做一次测试。需要区分结果时,使用按钮 valuereturnValueclose() 参数。

    1. 重新接入打开前初始化

    保留数据填充、状态重置、权限判断和打开前校验。这些是业务逻辑,不是原生 dialog 的替代对象。

    1. 最后处理动画和降级

    先确保无动画时功能正确,再加入开合过渡。最后确认不支持 showModal() 时,备用样式和关闭逻辑仍能工作。

    最后检查哪些代码值得保留

    改造完成后,可以用一个简单的判断标准复查:

    • 这段代码是否在控制弹窗承载的业务内容
    • 这段代码是否在决定弹窗什么时候打开
    • 这段代码是否在处理关闭后的业务结果
    • 这段代码是否属于项目明确要求的动画、点击空白关闭或降级体验

    如果答案都是否,而代码只是为了模拟模态状态、管理焦点、监听 Esc 或生成遮罩,那么它大概率已经可以删除。

    原生 dialog 的价值不在于让所有弹窗代码消失,而在于把最容易写错、也最适合交给浏览器处理的部分收回来。WordPress 主题和插件中的改造重点,应放在缩小自定义代码的职责:让浏览器管理模态交互,让 JavaScript 专注于打开条件、数据流转、业务结果和站点真正需要的视觉效果。

    声明:原创文章请勿转载,如需转载请注明出处!

    下一篇

    没有了,已经是最新文章

    • 抢沙发

    请登陆后再发表您的观点吧!

    账号登陆

    快捷登陆