手写diolog
面试官:"假设现在需要你从零实现一个Dialog弹窗组件,你会怎么设计?需要考虑哪些关键点?"
很多三方库已经封装了弹窗,给你自己从零封装一个弹窗组件,你会怎么做?
接下来来看看如何自行封装一个属于自己的弹窗组件。
利用浏览器天生支持的弹窗功能dialog: 一种普通弹窗、一种模态弹窗。
模态弹窗:1、半透明;2、毛玻璃;3、tab键切换(切换不到背后页面的文本框。);4、固定滚动条。
三点:
居中、
控制tab、
放到顶层。
// 包裹一个dialog元素
<dialog id="myDialog">
<div class="dialog-content">
<div class="dialog-header">
<h2 class="dialog-title">对话框标题</h2>
<button class="close-button" onclick="closeDialog()">✕</button>
</div>
<div class="dialog-body">
<p>这是对话框的内容区域。</p>
<p>你可以在这里放置任何 HTML 内容。</p>
<input type="text" placeholder="对话框中的输入框">
</div>
</div>
</dialog>
默认是关的。
点击按钮弹出弹窗:
// 给dialog取个id,获取的时候用id获取
const dialog = document.getElementById('myDialog');
// 普通弹窗 弹出来 事件是show
function openNormalDialog() {
dialog.show();
}
// 模态弹窗 弹出来 事件是showModal
function openModalDialog() {
dialog.showModal();
}
// 无论普通还是模态,都是close关闭弹窗
function closeDialog() {
dialog.close();
}
再看一下上面的dialog普通和模态的效果对比:
模态对话框背后是个半透明的背景:
这个透明的玻璃框有什么作用呢:
1、 背后按钮按不了了;
2、 tab键切换不用我们考虑了,原生帮我们做好了;
3、 如果底面那一层有input,点不了,用tab也切不了;(这点的话如果我们自己去做,是很难去做的,用原生,这些玩意都不用我们去考虑,浏览器原生天生就支持)。
弹窗永远是顶层,如果自己去定的话z-index的话,会有设得越来越大的可能性,有可能页面的弹窗太多了,自己设置了什么到最后也记不清了,导致会有一些失误导致达不到我们的效果。
原生这种dialog就很放心,保证是一定是最顶层,原因如下:
dialog后面这个一个top layer,
top layer在整个页面之外,所以它不是通过index去控制的,它本身就在整个页面之外,无论哪个元素,它的index再搞,都不会把它覆盖。
dialog的样默认样式:
/* 对话框样式 */
dialog {
padding: 20px;
border: none;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
设置成这样:
设置蒙层的样式:
蒙层是一个伪元素,
dialog了,里面有一个伪元素backdrop,就是蒙层:
/* 模态框背景样式 */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(1px); // 毛玻璃效果
}
封装类组件化
效果如下:
// 基于原生 <dialog> 元素实现的弹窗组件类
类结构
export default class MyDialog {
constructor(options) {
// 初始化配置
this.options = {
id: "my-dialog", // 默认ID
title: "对话框标题", // 默认标题
content: "<p>这是对话框的内容区域。</p>", // 默认内容
buttons: [ // 默认按钮配置
{ text: "取消", action: "cancel" },
{ text: "确定", action: "confirm" },
],
...options, // 合并用户自定义配置
};
this.init(); // 初始化弹窗
}
方法详解
init
init() {
this.createDialog(); // 创建DOM结构
this.bindEvents(); // 绑定事件
}
createDialog() —— 创建弹窗的dom结构:
reateDialog() {
// 创建dialog元素
const dialog = document.createElement("dialog");
dialog.id = this.options.id;
dialog.className = "dialog";
// 生成按钮HTML
const buttonsHTML = this.options.buttons
.map(
(btn) =>
`<button class="dialog__btn dialog__btn--${btn.action}">${btn.text}</button>`
)
.join("");
// 设置内部HTML结构
dialog.innerHTML = `
<div class="dialog__container">
<div class="dialog__header">
<h2 class="dialog__title">${this.options.title}</h2>
<button class="dialog__close" aria-label="关闭弹窗">x</button>
</div>
<div class="dialog__body">${this.options.content}</div>
<div >${buttonsHTML}</div> <!-- 注意:这里缺少了footer的class -->
</div>
`;
// 添加到body
document.body.appendChild(dialog);
this.dialog = dialog; // 保存dialog引用
}
bindEvents() —— 绑定事件处理函数:
bindEvents() {
// 关闭按钮事件
this.dialog
.querySelector(".dialog__close")
.addEventListener("click", () => this.close());
// 为每个按钮绑定事件
this.options.buttons.forEach((btn) => {
const btnEl = this.dialog.querySelector(`.dialog__btn--${btn.action}`);
if (btnEl) {
btnEl.addEventListener("click", () => {
// 如果有回调函数则执行
if (typeof btn.callback === "function") {
btn.callback();
}
this.close(); // 关闭弹窗
});
}
});
}
弹窗控制方法:
// 打开弹窗
open(modal = false) {
if (modal) {
this.dialog.showModal(); // 模态方式打开
} else {
this.dialog.show(); // 非模态方式打开
}
}
// 关闭弹窗
close() {
this.dialog.close();
}
弹窗实例化
<button id="openDialog">打开对话框</button>
<script type="module">
import MyDialog from './index.js'; // es6 模块化语法 导入自定义的MyDialog类
// 使用示例
const myDialog = new MyDialog({
title: '自定义标题',
content: '<p>自定义内容</p><input type="text" placeholder="测试输入">',
buttons: [
{ text: '取消', action: 'cancel' },
{
text: '确定', action: 'confirm', callback: () => console.log('确认')
}
]
});
document.getElementById('openDialog').addEventListener('click', () => {
myDialog.open();
});
</script>
总结
面试官:"假设现在需要你从零实现一个Dialog弹窗组件,你会怎么设计?需要考虑哪些关键点?"
我:"我会优先考虑使用HTML5原生<dialog>元素来实现,
原因有三点:
首先它能自动处理焦点锁定,避免用户通过Tab键切换到背景内容;
其次它自带模态遮罩层,不需要额外实现;
最后它有浏览器原生的层级管理,不会出现z-index冲突问题。"
面试官:"有意思,那具体怎么封装呢?如何保证组件的可复用性?"
我:"我会设计一个类组件,通过配置化方式支持自定义标题、内容和按钮。
关键实现包括:
1)动态创建dialog DOM结构;
2)事件绑定机制;
3)两种打开模式(模态/非模态)"
原生
的四大优势:✓ 自动焦点管理 - 解决Tab键穿透问题
✓ 原生遮罩层 - ::backdrop伪元素轻松实现
✓ 顶级层叠 - 永远在最上层
✓ 浏览器级性能优化 - 比JS实现的更流畅
| 方案 | 优点 | 缺点 |
|---|---|---|
| 第三方库 | 功能丰富、兼容性好 | 体积大、定制困难 |
| 原生dialog手动实现 | 零依赖、性能好、功能完整,完全可控 | 兼容性要求IE11+,实现成本高、易有缺陷 |