第一部分:核心概念理解
在开始编码前,我们必须明白两个核心组件及其关系:Modal (模态框) 和 Popper.js (定位工具)。

什么是 Modal (模态框)?
Modal 是一个覆盖在父窗口上的子窗口,通常用于显示重要信息、表单或确认对话框,它的特点是:
- 模态性:打开 Modal 时,父窗口通常被遮罩,用户无法与父窗口交互,必须先处理 Modal。
- 居中显示:最常见的样式是水平垂直居中在屏幕中央。
Bootstrap 的 modal 组件就是一个非常成熟的 Modal 实现。
什么是 Popper.js?
Popper.js 是一个强大的、专门用于元素定位的 JavaScript 库,它的核心功能是:根据一个“参考元素”(reference element)的位置,智能地“弹出”另一个“弹出元素”(popper element),并确保其始终在视口内可见。
它的关键特性:

- 智能定位:默认会尝试将弹出元素放在参考元素的下方,如果下方空间不足,它会自动尝试上方、左侧或右侧。
- 防止溢出:如果弹出元素可能会超出浏览器窗口的边界,Popper.js 会自动调整其位置,确保用户能看到完整内容。
- 动态更新:当页面滚动或窗口大小改变时,Popper.js 可以重新计算位置,让弹出元素始终“粘”在参考元素旁边。
两者的关系:为什么叫 "PopModal"?
"PopModal" 并不是一个官方的库名,而是社区对一种常见用法的通俗叫法:使用 Popper.js 来增强和精确定位一个 Modal 组件。
传统的 Modal 总是居中显示,但在很多复杂场景下,我们希望 Modal 能出现在某个特定元素(如按钮、表格行)的旁边,而不是屏幕中央,这时,Popper.js 就派上用场了。
- Modal 负责“遮罩层”、“样式”和“显示/隐藏逻辑”。
- Popper.js 负责“把 Modal 精确地定位到某个元素旁边”。
第二部分:基础实现 - 使用 Popper.js 定位一个自定义 Div
我们先不依赖 Bootstrap,只用原生 HTML、CSS 和 Popper.js 来实现一个最基础的 PopModal,这能帮助我们最清晰地理解其工作原理。

步骤 1:准备 HTML 结构
我们需要三个部分:
- 一个触发器(按钮)。
- 一个弹出元素(我们将把它做成一个类似 Modal 的盒子)。
- 一个遮罩层,让背景变暗。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">基础 PopModal 教程</title>
<style>
/* 基础样式 */
body {
font-family: sans-serif;
padding: 50px;
}
/* 触发器按钮样式 */
.trigger-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* 遮罩层样式 */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: none; /* 默认隐藏 */
}
/* 弹出元素(Modal 内容)样式 */
.popper-modal {
position: absolute; /* Popper.js 会修改这个 */
background-color: white;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
width: 250px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
z-index: 1001; /* 确保在遮罩层之上 */
display: none; /* 默认隐藏 */
}
</style>
</head>
<body>
<h1>Popper.js Modal 教程</h1>
<p>点击下面的按钮,一个定位好的 Modal 将出现在按钮旁边。</p>
<!-- 1. 触发器 -->
<button id="myButton" class="trigger-btn">显示 PopModal</button>
<!-- 2. 遮罩层 -->
<div id="backdrop" class="modal-backdrop"></div>
<!-- 3. 弹出元素 -->
<div id="myPopperModal" class="popper-modal">
<h3>这是一个 PopModal!</h3>
<p>我是由 Popper.js 精确定位的。</p>
<button id="closeModal">关闭</button>
</div>
<!-- 引入 Popper.js 库 -->
<!-- 从 CDN 引入是最简单的方式 -->
<script src="https://unpkg.com/@popperjs/core@2/dist/umd/popper.min.js"></script>
<script>
// 获取 DOM 元素
const button = document.getElementById('myButton');
const modal = document.getElementById('myPopperModal');
const backdrop = document.getElementById('backdrop');
const closeModalBtn = document.getElementById('closeModal');
// 创建 Popper 实例
// 这是 Popper.js 的核心:告诉它以谁为参考,定位谁
const popperInstance = Popper.createPopper(button, modal, {
placement: 'bottom', // 定位在按钮的 'bottom'
// 其他配置选项...
});
// 显示 Modal 和遮罩层
function show() {
modal.style.display = 'block';
backdrop.style.display = 'block';
// 更新 Popper 位置,因为元素从 display:none 变成了 display:block
popperInstance.update();
}
// 隐藏 Modal 和遮罩层
function hide() {
modal.style.display = 'none';
backdrop.style.display = 'none';
}
// 绑定事件
button.addEventListener('click', show);
closeModalBtn.addEventListener('click', hide);
backdrop.addEventListener('click', hide); // 点击遮罩层也可以关闭
</script>
</body>
</html>
代码解析:
- HTML 结构:清晰定义了触发器、遮罩层和弹出元素。
- CSS:
.modal-backdrop使用position: fixed和半透明背景实现遮罩效果。.popper-modal初始position: absolute,Popper.js 会接管这个属性,动态计算top和left值。z-index确保了正确的层叠顺序。
- JavaScript:
Popper.createPopper(referenceElement, popperElement, options):这是创建 Popper 实例的核心方法。referenceElement: 参考元素,这里是我们的按钮button。popperElement: 要被定位的元素,这里是我们的modal。options: 一个配置对象,placement: 'bottom'表示我们希望modal出现在button的下方。
popperInstance.update():非常重要!当弹出元素的可见状态改变(如从display: none变为display: block)或者页面布局发生变化时,必须调用此方法来让 Popper.js 重新计算位置。- 事件绑定:为显示和关闭添加了简单的事件处理。
第三部分:进阶用法 - 结合 Bootstrap Modal
在实际项目中,我们更倾向于使用像 Bootstrap 这样的成熟框架,下面我们来看看如何将 Popper.js 与 Bootstrap 的 Modal 结合。
关键点:Bootstrap Modal 的默认样式是 display: none 和 position: fixed,要让 Popper.js 控制它,我们需要做两件事:
- 临时改变其
position为absolute。 - 在显示时调用
popperInstance.update()。
步骤 1:准备 Bootstrap 和 Popper.js
确保你引入了 Bootstrap 的 CSS 和 JS,以及 Popper.js。注意:Bootstrap 5 自带了 Popper.js,所以如果你只引入 Bootstrap,就已经包含了 Popper。
<!-- 引入 Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- 引入 Bootstrap JS (包含 Popper.js) --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
步骤 2:HTML 结构
使用 Bootstrap 的标准 Modal 结构。
<!-- 触发器按钮 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
Launch static backdrop modal
</button>
<!-- Bootstrap Modal -->
<div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Understood</button>
</div>
</div>
</div>
</div>
步骤 3:JavaScript 定位逻辑
我们需要在 Bootstrap Modal 显示后,用 Popper.js 来控制它的位置。
document.addEventListener('DOMContentLoaded', () => {
// 获取触发器和 Modal 元素
const triggerButton = document.querySelector('[data-bs-toggle="modal"]');
const modalElement = document.getElementById('staticBackdrop');
const modalDialog = modalElement.querySelector('.modal-dialog');
// 创建 Popper 实例
// 我们将定位 .modal-dialog,而不是整个 .modal
const popperInstance = Popper.createPopper(triggerButton, modalDialog, {
placement: 'right', // 放在按钮的右边
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8], // 与按钮的距离,[x, y]
},
},
],
});
// 监听 Bootstrap Modal 的 'shown.bs.modal' 事件
// 这个事件在 Modal 完全显示并过渡后触发
modalElement.addEventListener('shown.bs.modal', () => {
// 1. 临时修改 Modal 的定位方式
modalElement.style.position = 'absolute';
// 2. 更新 Popper 实例,使其生效
popperInstance.update();
});
// 监听 Bootstrap Modal 的 'hidden.bs.modal' 事件
// 在 Modal 隐藏后,恢复其原始样式
modalElement.addEventListener('hidden.bs.modal', () => {
modalElement.style.position = ''; // 恢复空,即 static
});
});
代码解析:
- 定位
.modal-dialog:我们定位的是.modal-dialog,因为这是 Modal 内容的实际容器,而不是外层的.modal。 shown.bs.modal事件:这是 Bootstrap 提供的事件,在动画结束后触发,我们在这里执行 Popper 的操作,确保元素已经渲染完毕。- 临时修改
position:这是最关键的一步,Bootstrap 的 Modal 依赖position: fixed来实现居中,我们必须先将其改为absolute,Popper.js 的position: absolute定位才能生效。 modifiers:Popper.js 的modifiers功能非常强大,这里我们使用了offset修饰符,可以在默认定位的基础上增加偏移量,让 Modal 和按钮之间有一点间距,看起来更美观。hidden.bs.modal事件:在 Modal 隐藏后,我们将position属性恢复原样,以免影响下次 Bootstrap 自己的居中逻辑。
第四部分:最佳实践与常见问题
响应式设计
Popper.js 本身是响应式的,当窗口大小改变或页面滚动时,它会自动更新位置,你只需要确保在必要时调用 popperInstance.update() 即可。
销毁 Popper 实例
Modal 或触发器元素被从 DOM 中移除,你应该销毁 Popper 实例以避免内存泄漏。
// 在不再需要时销毁 popperInstance.destroy();
Modal 内部的内容高度会发生变化(通过 AJAX 加载数据),你需要在内容加载完成后调用 popperInstance.update()。
// 假设你通过 AJAX 加载了内容到 modal-body
fetch('/api/data')
.then(response => response.text())
.then(data => {
document.querySelector('.modal-body').innerHTML = data;
popperInstance.update(); // 内容变了,重新计算位置
});
常见问题:定位不生效或闪烁**
- 原因:通常是因为在元素
display: none时就创建了 Popper 实例,或者没有在元素显示后调用update()。 - 解决方案:确保在
shown.bs.modal或类似的生命周期事件中调用popperInstance.update()。
替代方案:Floating UI
Popper.js 的原作者已经创建了一个新的库叫 Floating UI,它被认为是 Popper.js 的继任者,功能更强大,API 更现代化,并且不再依赖 DOM 环境(可以在 Node.js 或 SSR 中使用),对于新项目,强烈建议直接使用 Floating UI,其用法与 Popper.js 非常相似。
通过本教程,你应该已经掌握了:
- 核心概念:Modal 负责样式和交互,Popper.js 负责智能定位。
- 基础实现:如何用原生 HTML/CSS/JS 和 Popper.js 创建一个简单的 PopModal。
- 进阶用法:如何与 Bootstrap 等 UI 框架结合,关键在于处理
position属性和生命周期事件。 - 最佳实践:如何处理响应式、动态内容和销毁实例。
你可以开始在自己的项目中灵活运用 Popper.js 来创建更加精致和用户友好的弹出界面了!
