聚海凝涛 發表於 2025-11-14 13:44:00

纯HTML + CSS + JS 实现Popup弹窗

<p>在 Web 开发中,弹窗(Popup)是一种极其常见的交互组件,广泛用于:</p>
<ul>
<li>表单提交确认</li>
<li>删除操作二次确认</li>
<li>登录/注册入口</li>
<li>信息提示或警告</li>
</ul>
<p>虽然现在有大量 UI 框架(如 Element UI、Ant Design、Bootstrap)提供现成的弹窗组件,但<strong>理解其底层实现原理</strong>,不仅能让你在无框架环境下快速构建功能,还能加深对 DOM 操作、事件处理和 CSS 布局的理解。</p>
<p>本文将基于你提供的代码片段,<strong>从零讲解如何用纯 HTML/CSS/JS 实现一个专业级的 Popup 弹窗</strong>,并扩展出生产环境中的实用技巧。</p>
<hr>
<h2 id="-一基础结构解析">📌 一、基础结构解析</h2>
<p>popup的弹窗代码片段</p>
<pre><code class="language-html">&lt;!-- 蒙版 --&gt;
&lt;div id="mask"&gt;&lt;/div&gt;

&lt;!-- 弹窗容器 --&gt;
&lt;div id="popup"&gt;
&lt;div class="popup-header"&gt;标题&lt;/div&gt;
&lt;div class="popup-body"&gt;内容&lt;/div&gt;
&lt;div class="popup-footer"&gt;
    &lt;button id="close"&gt;关闭&lt;/button&gt;
    &lt;button id="confirm"&gt;确定&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h3 id="-关键设计思想">🔍 关键设计思想</h3>
<table>
<thead>
<tr>
<th>元素</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#mask</code></td>
<td>半透明遮罩层,阻止用户操作背景页面</td>
</tr>
<tr>
<td><code>#popup</code></td>
<td>弹窗主体,居中显示</td>
</tr>
<tr>
<td><code>.popup-header/body/footer</code></td>
<td>语义化分区,便于样式控制</td>
</tr>
</tbody>
</table>
<blockquote>
<p>💡 这种“蒙版 + 弹窗”的组合,是实现<strong>模态对话框(Modal)</strong> 的标准做法。</p>
</blockquote>
<hr>
<h2 id="-二css-样式详解">📌 二、CSS 样式详解</h2>
<h3 id="21-蒙版mask关键样式">2.1 蒙版(Mask)关键样式</h3>
<pre><code class="language-css">#mask {
position: fixed;      /* 固定定位,脱离文档流 */
top: 0; left: 0;
width: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* 半透黑 */
display: none;          /* 默认隐藏 */
z-index: 1000;          /* 层级高于普通内容 */
}
</code></pre>
<ul>
<li><code>position: fixed</code>:确保蒙版始终覆盖整个视口,即使页面滚动也不移位。</li>
<li><code>rgba(0,0,0,0.5)</code>:黑色透明度 50%,既遮挡背景又不完全遮蔽。</li>
</ul>
<h3 id="22-弹窗popup居中秘诀">2.2 弹窗(Popup)居中秘诀</h3>
<pre><code class="language-css">#popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 精准居中 */
width: 400px;
z-index: 1001; /* 高于蒙版 */
}
</code></pre>
<blockquote>
<p>✅ <strong>为什么不用 <code>margin: auto</code>?</strong><br>
因为 <code>fixed</code> 定位下 <code>margin: auto</code> 在某些浏览器中表现不稳定。<br>
<code>transform: translate(-50%, -50%)</code> 是目前最可靠的垂直+水平居中方案。</p>
</blockquote>
<hr>
<h2 id="-三javascript-交互逻辑">📌 三、JavaScript 交互逻辑</h2>
<pre><code class="language-js">// 显示
btn.addEventListener('click', () =&gt; {
mask.style.display = 'block';
popup.style.display = 'block';
});

// 关闭(按钮 + 蒙版点击)
close.addEventListener('click', hidePopup);
mask.addEventListener('click', hidePopup);

function hidePopup() {
mask.style.display = 'none';
popup.style.display = 'none';
}
</code></pre>
<h3 id="️-注意事项">⚠️ 注意事项</h3>
<ul>
<li><strong>事件委托更优?</strong>:此处元素固定,直接绑定即可。</li>
<li><strong>键盘支持(ESC 关闭)</strong>:生产环境建议加上。</li>
</ul>
<hr>
<h2 id="-四升级版添加-esc-键关闭--动画效果">📌 四、升级版:添加 ESC 键关闭 &amp; 动画效果</h2>
<h3 id="41-支持按-esc-关闭弹窗">4.1 支持按 ESC 关闭弹窗</h3>
<pre><code class="language-js">// 新增:监听键盘事件
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' &amp;&amp; popup.style.display === 'block') {
    hidePopup();
}
});
</code></pre>
<h3 id="42-添加淡入淡出动画提升用户体验">4.2 添加淡入淡出动画(提升用户体验)</h3>
<h4 id="修改-css">修改 CSS</h4>
<pre><code class="language-css">/* 蒙版动画 */
#mask {
opacity: 0;
transition: opacity 0.3s ease;
}

#mask.show {
opacity: 1;
}

/* 弹窗动画 */
#popup {
opacity: 0;
transform: translate(-50%, -60%); /* 初始位置略高 */
transition: all 0.3s ease;
}

#popup.show {
opacity: 1;
transform: translate(-50%, -50%);
}
</code></pre>
<h4 id="修改-js">修改 JS</h4>
<pre><code class="language-js">function showPopup() {
mask.classList.add('show');
popup.classList.add('show');
// 必须先设为 block 再加类,否则 transition 不生效
mask.style.display = 'block';
popup.style.display = 'block';
}

function hidePopup() {
mask.classList.remove('show');
popup.classList.remove('show');

// 动画结束后再隐藏(避免闪现)
setTimeout(() =&gt; {
    if (!mask.classList.contains('show')) {
      mask.style.display = 'none';
      popup.style.display = 'none';
    }
}, 300);
}
</code></pre>
<blockquote>
<p>✅ <strong>动画原理</strong>:通过 <code>opacity</code> 和 <code>transform</code> 实现平滑过渡,比 <code>display</code> 切换更自然。</p>
</blockquote>
<hr>
<h2 id="-五封装成可复用函数面向未来">📌 五、封装成可复用函数(面向未来)</h2>
<p>为了在多个页面复用,我们可以将其封装:</p>
<pre><code class="language-js">function createPopup(title, content, onConfirm) {
const popup = document.createElement('div');
popup.innerHTML = `
    &lt;div class="popup-header"&gt;${title}&lt;/div&gt;
    &lt;div class="popup-body"&gt;${content}&lt;/div&gt;
    &lt;div class="popup-footer"&gt;
      &lt;button class="popup-cancel"&gt;取消&lt;/button&gt;
      &lt;button class="popup-confirm"&gt;确定&lt;/button&gt;
    &lt;/div&gt;
`;
popup.id = 'popup';
document.body.appendChild(popup);

// 绑定事件...
}
</code></pre>
<p>但更推荐的方式是:<strong>将 HTML 结构保留在页面中,通过 JS 控制显隐和内容更新</strong>,避免重复创建 DOM。</p>
<hr>
<h2 id="-六生产环境最佳实践">📌 六、生产环境最佳实践</h2>
<table>
<thead>
<tr>
<th>实践</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>✅ <strong>语义化 HTML</strong></td>
<td>使用 <code>&lt;dialog&gt;</code> 标签(现代浏览器支持)更语义化,但兼容性需考虑</td>
</tr>
<tr>
<td>✅ <strong>焦点管理</strong></td>
<td>弹窗打开时,将焦点锁定在弹窗内(防止背景滚动、提升无障碍体验)</td>
</tr>
<tr>
<td>✅ <strong>防止滚动穿透</strong></td>
<td>弹窗开启时,给 <code>body</code> 添加 <code>overflow: hidden</code></td>
</tr>
<tr>
<td>✅ <strong>A11Y 可访问性</strong></td>
<td>添加 <code>role="dialog"</code>、<code>aria-labelledby</code> 等属性</td>
</tr>
<tr>
<td>✅ <strong>避免 inline style</strong></td>
<td>尽量用 class 切换,而非直接操作 <code>style.display</code></td>
</tr>
</tbody>
</table>
<h3 id="示例防止背景滚动">示例:防止背景滚动</h3>
<pre><code class="language-js">function showPopup() {
document.body.style.overflow = 'hidden'; // 禁止背景滚动
mask.style.display = 'block';
popup.style.display = 'block';
}

function hidePopup() {
document.body.style.overflow = ''; // 恢复滚动
mask.style.display = 'none';
popup.style.display = 'none';
}
</code></pre>
<h3 id="完整代码">完整代码</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;test popup&lt;/title&gt;
    &lt;style&gt;
      /* 蒙版样式 */
      #mask {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            display: none;
            z-index: 1000;
      }

      /* 弹窗容器样式 */
      #popup {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 400px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            display: none;
            z-index: 1001;
            overflow: hidden;
      }

      /* 弹窗标题部分 */
      .popup-header {
            padding: 16px 20px;
            background-color: #f5f5f5;
            border-bottom: 1px solid #e0e0e0;
            font-size: 18px;
            font-weight: bold;
      }

      /* 弹窗内容部分 */
      .popup-body {
            padding: 20px;
            min-height: 100px;
      }

      /* 弹窗按钮部分 */
      .popup-footer {
            padding: 16px 20px;
            background-color: #f5f5f5;
            border-top: 1px solid #e0e0e0;
            text-align: right;
      }

      .popup-footer button {
            margin-left: 10px;
            padding: 8px 16px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background-color: #fff;
            cursor: pointer;
      }

      .popup-footer button:hover {
            background-color: #f0f0f0;
      }

      /* 主画面按钮样式 */
      #btn {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
      }
    &lt;/style&gt;
&lt;/head&gt;

&lt;body&gt;
&lt;!--主画面的UI--&gt;
&lt;div&gt;
    &lt;button id="btn"&gt;弹窗&lt;/button&gt;
&lt;/div&gt;

&lt;!--弹窗画面的UI--&gt;
&lt;div id="mask"&gt;&lt;/div&gt;
&lt;div id="popup"&gt;
    &lt;div class="popup-header"&gt;
      弹窗标题
    &lt;/div&gt;
    &lt;div class="popup-body"&gt;
      &lt;p&gt;这是弹窗的内容区域&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class="popup-footer"&gt;
      &lt;button id="close"&gt;关闭&lt;/button&gt;
      &lt;button id="confirm"&gt;确定&lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;!--弹窗画面的UI--&gt;
&lt;script&gt;
    var btn = document.getElementById('btn');
    var mask = document.getElementById('mask');
    var popup = document.getElementById('popup');
    var close = document.getElementById('close');

    // 显示弹窗
    btn.addEventListener('click', function() {
      mask.style.display = 'block';
      popup.style.display = 'block';
    });

    // 关闭弹窗
    close.addEventListener('click', function() {
      mask.style.display = 'none';
      popup.style.display = 'none';
    });

    // 点击蒙版关闭弹窗
    mask.addEventListener('click', function() {
      mask.style.display = 'none';
      popup.style.display = 'none';
    });
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

</code></pre>
<h3 id="效果图">效果图</h3>
<p><img src="https://img2024.cnblogs.com/blog/1994787/202511/1994787-20251114134321727-950390615.png" alt="image" loading="lazy"></p>
<hr>
<h2 id="-总结">✅ 总结</h2>
<p>通过本文,你掌握了:</p>
<ol>
<li><strong>Popup 弹窗的核心结构</strong>:蒙版 + 弹窗容器</li>
<li><strong>精准居中技巧</strong>:<code>transform: translate(-50%, -50%)</code></li>
<li><strong>交互逻辑实现</strong>:显示/隐藏、蒙版点击关闭、ESC 键支持</li>
<li><strong>用户体验优化</strong>:淡入淡出动画、防止滚动穿透</li>
<li><strong>生产级注意事项</strong>:可访问性、焦点管理、代码复用</li>
</ol>
<blockquote>
<p>💡 <strong>记住</strong>:优秀的前端开发,不仅在于“能实现”,更在于“实现得优雅、健壮、可维护”。</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/sailCoding/p/19221831
頁: [1]
查看完整版本: 纯HTML + CSS + JS 实现Popup弹窗