不用框架创建一个弹窗组件
本文将介绍不通过框架(vue
或者react
等)创建一个弹窗组件。
创建一个HTML文件
首先先创建一个html
文件,并包含以下弹窗组件的结构代码:
<div class="vanilla-modal">
<div class="modal">
<div class="modal-inner">
<div id="modal-content"></div>
</div>
</div>
</div>
创建CSS文件
并创建一个css
文件为上面的html
结构添加样式。
.modal{
display: block;
position: fixed;
content: "";
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: -1;
opacity: 0;
transition: opacity 0.2s, z-index 0s 0.2s;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.modal > * {
display: inline-block;
white-space: normal;
vertical-align: middle;
text-align: left;
}
.modal:before {
display: inline-block;
overflow: hidden;
width: 0;
height: 100%;
vertical-align: middle;
content: "";
}
.modal-visible .modal {
z-index: 9999;
opacity: 1;
transition: opacity 0.2s;
}
.modal-inner {
position: relative;
overflow: hidden;
max-width: 90%;
max-height: 90%;
background: #fff;
z-index: -1;
opacity: 0;
transform: scale(0);
transition: opacity 0.2s, transform 0.2s, z-index 0s 0.2s;
min-width: 500px;
border-radius: 6px;
}
.modal-visible .modal-inner {
z-index: 100;
opacity: 1;
transform: scale(1);
transition: opacity 0.2s, transform 0.2s;
}
#modal-content{
padding: 50px 70px;
}
编写script控制弹窗组件
下面的代码允许我们初始化弹窗,打开它,然后关闭它。弹窗将监听模态外部的点击,点击关闭按钮,按下转义键,并在模态内部处理标签导航。
function outsideClick(e) {
if (e.target.closest(".modal-inner")) {
return;
}
const modalVisible = document.querySelector(".modal-visible");
if (modalVisible) {
closeModal();
}
}
function escKey(e) {
if (e.keyCode == 27) {
closeModal();
}
}
function closeClick(e) {
if (e.target.classList.contains("closeModal")) {
closeModal();
}
}
function trapTabKey(e) {
const vanillaModal = document.querySelector(".vanilla-modal");
const FOCUSABLE_ELEMENTS = [
"a[href]",
"area[href]",
'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
"select:not([disabled]):not([aria-hidden])",
"textarea:not([disabled]):not([aria-hidden])",
"button:not([disabled]):not([aria-hidden])",
"iframe",
"object",
"embed",
"[contenteditable]",
'[tabindex]:not([tabindex^="-"])',
];
const nodes = vanillaModal.querySelectorAll(FOCUSABLE_ELEMENTS);
let focusableNodes = Array(...nodes);
if (focusableNodes.length === 0) return;
focusableNodes = focusableNodes.filter((node) => {
return node.offsetParent !== null;
});
// if disableFocus is true
if (!vanillaModal.contains(document.activeElement)) {
focusableNodes[0].focus();
} else {
const focusedItemIndex = focusableNodes.indexOf(document.activeElement);
if (e.shiftKey && focusedItemIndex === 0) {
focusableNodes[focusableNodes.length - 1].focus();
e.preventDefault();
}
if (
!e.shiftKey &&
focusableNodes.length > 0 &&
focusedItemIndex === focusableNodes.length - 1
) {
focusableNodes[0].focus();
e.preventDefault();
}
}
}
function closeModal() {
const vanillaModal = document.querySelector(".vanilla-modal");
if (vanillaModal) {
vanillaModal.classList.remove("modal-visible");
document.getElementById("modal-content").innerHTML = "";
document.getElementById("modal-content").style = "";
}
document.removeEventListener("keydown", escKey);
document.removeEventListener("click", outsideClick, true);
document.removeEventListener("click", closeClick);
document.removeEventListener("keydown", trapTabKey);
}
const modal = {
init: function () {
const prerendredModal = document.createElement("div");
prerendredModal.classList.add("vanilla-modal");
const htmlModal = `
<div class="modal">
<div class="modal-inner"
><div id="modal-content"></div></div></div>`;
prerendredModal.innerHTML = htmlModal;
document.body.appendChild(prerendredModal);
},
open: function (idContent, option = {
default: null }) {
let vanillaModal = document.querySelector(".vanilla-modal");
if (!vanillaModal) {
console.log("there is no vanilla modal class");
modal.init();
vanillaModal = document.querySelector(".vanilla-modal");
}
const content = document.getElementById(idContent);
let currentModalContent = content.cloneNode(true);
currentModalContent.classList.add("current-modal");
currentModalContent.style = "";
document.getElementById("modal-content").appendChild(currentModalContent);
if (!option.default) {
if (option.width && option.height) {
document.getElementById("modal-content").style.width = option.width;
document.getElementById("modal-content").style.height = option.height;
}
}
vanillaModal.classList.add("modal-visible");
document.addEventListener("click", outsideClick, true);
document.addEventListener("keydown", escKey);
document.addEventListener("keydown", trapTabKey);
document
.getElementById("modal-content")
.addEventListener("click", closeClick);
},
close: function () {
closeModal();
},
};
我们来详细介绍以下上面的代码:
-
outsideClick
函数负责处理弹窗外的点击。如果点击目标不在弹窗内,它会触发closeModal
功能关闭弹窗。 -
escKey
函数检查是否按了转义键(ESC
),如果按了,它会调用closeModal
函数。 -
closeClick
函数在弹窗中监听关闭按钮上的单击事件。如果点击目标具有类"闭式",它将触发closeModal
函数。 -
trapTabKey
函数用于在弹窗中捕获键盘焦点.它首先基于提供的数组检索弹窗内所有可聚焦的元素,然后过滤掉当前不可见的任何元素。- 如果没有可聚焦元素,它只会返回。
- 如果弹窗当前不包含焦点(这意味着弹窗当前没有一个元素),它将把焦点放在第一个可焦点元素上。
- 如果弹窗当前包含焦点,它处理选项卡导航逻辑:
-
- 如果转移键与选项卡一起按,它将焦点移动到最后一个可焦点元素,并防止默认的选项卡行为。
-
- 如果只按了选项卡键,而当前的焦点元素是最后一个可集中的元素,则它将焦点移动到第一个元素,并防止默认的选项卡行为。
5.closeModal
函数负责关闭弹窗.它找到弹窗元素并删除与弹窗可见性相关的类和内容。它还删除了事件侦听器的键,点击外部的弹窗,并点击关闭按钮。
- 如果只按了选项卡键,而当前的焦点元素是最后一个可集中的元素,则它将焦点移动到第一个元素,并防止默认的选项卡行为。
-
modal
对象有三种方法:init()
这个方法负责初始化弹窗。它创建了弹窗的HTML
结构并将其附加到文档体中。open(idContent, option)
这个方法用来打开弹窗。需要两个参数:-
idContent
(必须):包含要在模式中显示的内容的元素的ID
。
-
option
(可选):允许指定附加选项的对象。目前它只支持设置width
和height
。
close()
这个方法直接调用closeModal
函数关闭弹窗。
示例
创建一个h1
元素作为弹窗内容:
<h1 id="title">弹窗内容</h1>
通过open
方法打开弹窗:
modal.open("title");