JS-原生实战:手把手带你封装一个高性能模态框(Modal)组件

82 阅读3分钟

前言

在网页开发中,弹框(Modal)是最常见的交互组件之一。一个合格的弹框不仅要能“跳出来”,还需要处理好遮罩层、层级关系(z-index)、防滚动穿透以及用户便捷退出的体验。本文将从零开始带你实现一个具备丝滑动画效果的模态框。

一、 核心设计思路

实现弹框主要分为三个部分,它们的层级关系如下:

  1. 遮罩层 (Overlay) :铺满全屏的半透明背景,用于阻断用户对背景页面的操作,并突出弹框,初始diasplaynone,并使用position: fixed使其铺满屏幕。

  2. 内容盒 (Content) :位于遮罩层之上,水平垂直居中,承载具体信息,初始diasplaynone

  3. 控制逻辑

    • 打开:显示遮罩与弹框,同时锁定背景滚动,将遮罩层与内容盒的display属性均设置为block,并设置弹框内容盒子的z-index让其比遮罩层高。
    • 关闭:隐藏元素,恢复背景滚动。支持点击“关闭按钮”、点击“遮罩层”以及按下 “Esc 键”退出。

二、 关键技术点解析

1. 为什么使用 position: fixed 而非 absolute

在遮罩层和弹框的样式中,我们优先选择 fixed

  • absolute 是相对于最近的已定位祖先元素,如果页面很长,滚动后弹框可能会“飞走”。
  • fixed 是相对于浏览器视口(Viewport)定位,无论页面如何滚动,弹框始终保持在屏幕中央。

2. 完美的水平垂直居中

利用 top: 50%left: 50% 将左上角定位到中心,再配合 transform: translate(-50%, -50%) 将盒子向左和向上平移自身宽高的 50%,实现真正的精准居中。

3. 防止“滚动穿透”

当弹框打开时,用户依然可以滚动背景页面,这在用户体验上是不好的。

  • 解决方案:打开弹框时设置 document.body.style.overflow = "hidden"

三、 完整代码实现

以下是优化后的代码,加入了细腻的淡入(FadeIn)和滑入(SlideIn)动画。

<!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>
      body {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }

      /* 背景遮罩层 */
      .modal-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.6);
        z-index: 999;
        animation: fadeIn 0.3s ease-out;
      }

      /* 弹框样式 */
      .modal {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 50%;
        max-width: 500px;
        background-color: white;
        border-radius: 16px;
        z-index: 1000;
        padding: 32px;
        animation: slideIn 0.4s ease-out;
        overflow: hidden;
      }
      /* 动画效果 */
      @keyframes fadeIn {
        from {
          opacity: 0;
        }
        to {
          opacity: 1;
        }
      }

      /* 淡入动画 */
      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translate(-50%, -60%);
        }
        to {
          opacity: 1;
          transform: translate(-50%, -50%);
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <header>
        <h1>弹框组件演示</h1>
      </header>

      <section class="content-section">
        <button class="open-btn" id="openModalBtn">打开弹框</button>
      </section>
      <section style="height: 1000px;"></section>
    </div>

    <!-- 灰色背景遮罩层 -->
    <div class="modal-overlay" id="modalOverlay"></div>

    <!-- 弹框 -->
    <div class="modal" id="modal">
      <h2>欢迎使用弹框组件</h2>
      <button id="closeModalBtn">关闭弹框</button>
    </div>

    <script>
      // 获取DOM元素
      const openModalBtn = document.getElementById("openModalBtn"); //打开按钮
      const modalOverlay = document.getElementById("modalOverlay"); //遮罩层
      const modal = document.getElementById("modal"); //弹框
      const closeModalBtn = document.getElementById("closeModalBtn"); //关闭按钮

      // 打开弹框
      function openModal() {
        modalOverlay.style.display = "block";
        modal.style.display = "block";
        document.body.style.overflow = "hidden"; // 防止背景滚动
      }

      // 关闭弹框
      function closeModal() {
        modalOverlay.style.display = "none";
        modal.style.display = "none";
        document.body.style.overflow = "auto"; // 恢复滚动
      }

      // 给打开按钮绑定打开弹框操作
      openModalBtn.addEventListener("click", openModal);

      // 点击背景遮罩层关闭弹框
      modalOverlay.addEventListener("click", closeModal);

      // 按ESC键关闭弹框
      document.addEventListener("keydown", function (event) {
        if (event.key === "Escape" && modal.style.display === "block") {
          closeModal();
        }
      });

    </script>
  </body>
</html>