与其将受影响的 DOM 节点硬连接到它们的预期行为,也不必将 CSS 数据也烘焙到 JavaScript 代码中,而是可以提供一个 组件 (DOM) 和 模块 (JavaScript) 不仅仅是一个单一的想法。
最重要的是,通过 JavaScript 逻辑将使用的一组通用 css 类名来定位此行为,将动画行为完全留给 CSS。
当然,必须从更通用、结构更好的标记开始。尤其是嵌套对于如何定位布局规则至关重要,更重要的是,对于如何实现元素特定 css 转换的细粒度定位。
因此,OP 的原始 CSS 根据新标记进行了轻微重组。
OP 还可能会注意到 active、before-deactivation 和 deactivating 的类名称特定规则,每个规则都针对刚刚或仍然提升的模态的特定状态 组件,可以通过每个modal 组件根节点上的相关data-* 属性进行自定义。
modals-module 背后的理念是方便和灵活。模块内部按每个项目对应的元素节点存储modal 项目。
该模块只有get 和initialize 两种方法。后者通过其特定的data-component-modal 属性识别所有与modal 相关的元素节点,并创建一个特定于节点的modal-item。
因此,每个模态相关元素节点都可以被模块的get 方法使用,用于检索已经存在的modal-item,或者创建、存储和获取新元素-节点特定modal-item。
每个项目都拥有两个方法activate 和deactivate,它们根据其命名/措辞从字面上触发一个行为。
因此,组件方法允许在同一个文档中使用多个模态组件。它允许其他程序逻辑或组件通过它们的元素节点引用单独检索和使用modal-items,例如触发模态行为,例如提升/打开 (activate) 或关闭 (deactivate) 模态相关的元素节点。
最终可执行sn-p的main函数的实现确实示范了刚才所说的...
function main() {
modals.initialize();
document
.querySelectorAll('[data-activate-modal]')
.forEach(triggerNode => document
.querySelectorAll(triggerNode.dataset.activateModal)
.forEach(componentNode => triggerNode
.addEventListener('click', modals.get(componentNode).activate)
)
);
}
...modals 确实首先被初始化。然后是一些按钮,我们希望它们成为触发不同模式的触发器。这样的按钮/触发器由其data-activate-modal 属性标识。后者的值将用作目标模态的元素查询。例如像...这样的按钮
<button data-activate-modal='#warningModal'>
Open Warning Modal
</button>
... 确实针对'#warningModal' 查询的任何模态,由于使用了基于id 的选择器,因此该示例的原因只是一个唯一的模态组件。
最后,任何触发元素都将通过activate-ing 处理此类触发器上的每个相关项目来处理其任何目标modal-items,例如click 事件。
但是,通过相关的元素节点引用存储和检索模态项的方法还有更多选择。
// `modals` module.
const modals = (function () {
function getSafeInteger(value) {
value = parseInt(value, 10);
return Number.isSafeInteger(value) ? value : 0;
}
function activateBoundModalTarget() {
this.classList.add('active');
}
function deactivateBoundModalTarget() {
const modalNode = this;
let {
deactivationDuration: delayDefault,
deactivationDelay: delayBefore,
} = modalNode.dataset;
delayDefault = getSafeInteger(delayDefault);
delayBefore = getSafeInteger(delayBefore);
modalNode.classList.add('before-deactivation', 'deactivating');
setTimeout(() => {
modalNode.classList.remove('active', 'before-deactivation');
setTimeout(() =>
modalNode.classList.remove('deactivating'), delayDefault
);
}, delayBefore);
}
// `modal` item/type factory
function createModal(rootNode) {
const activate = activateBoundModalTarget.bind(rootNode);
const deactivate = deactivateBoundModalTarget.bind(rootNode);
rootNode
.querySelectorAll('[data-trigger-inactive]')
.forEach(triggerNode => triggerNode
.addEventListener('click', deactivate)
);
return {
rootNode,
activate,
deactivate
}
}
const storage = new WeakMap;
function getModal(rootNode) {
let modal = storage.get(rootNode);
if (
!modal &&
(rootNode instanceof HTMLElement) &&
rootNode.hasAttribute('data-component-modal')
) {
if (modal = createModal(rootNode)) {
storage.set(rootNode, modal);
}
}
return modal ?? null;
}
function initialize() {
document
.querySelectorAll('[data-component-modal]')
.forEach(getModal);
}
// export module.
return {
get: getModal,
initialize,
};
}());
function main() {
modals.initialize();
document
.querySelectorAll('[data-activate-modal]')
.forEach(triggerNode => document
.querySelectorAll(triggerNode.dataset.activateModal)
.forEach(componentNode => triggerNode
.addEventListener('click', modals.get(componentNode).activate)
)
);
}
main();
* {
margin: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
}
[data-component-modal] {
z-index: -1;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
[data-component-modal] .background {
opacity: 0;
z-index: 0;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100vh;
background: rgba(192, 192, 192, 0.9);
transition: all 0.5s;
}
[data-component-modal] .content {
zoom: .8;
opacity: 0;
z-index: 1;
position: absolute;
left: 50%;
top: 50%;
width: 300px;
background: #fff;
overflow: hidden;
transition: all 0.5s;
transform: translate(-50%, -50%) scale(1);
}
[data-component-modal].deactivating,
[data-component-modal].active {
z-index: 99;
}
[data-component-modal].active .background,
[data-component-modal].active .content {
opacity: 1;
}
[data-component-modal].active.before-deactivation .content {
transform: translate(-50%, -50%) scale(1.1);
}
[data-component-modal].error .background {
background: rgba(255, 192, 192, 0.9);
}
[data-component-modal] .content h1 {
background: #E6E6E6;
font-size: 20px;
padding: 0.5em;
border-bottom: 1px solid #C0C0C0;
}
[data-component-modal] .content p {
min-height: 70px;
padding: 0.5em;
font-weight: 400;
font-size: 15px;
}
[data-component-modal].error .content h1,
[data-component-modal].error .content p {
color: #ff0C0C;
}
[data-component-modal] .content button {
display: block;
width: 50%;
padding: 0.5em 1em;
background: #EDB44C;
color: #fff;
font-size: 15px;
margin: 0 auto 0.5em auto;
border: 1px solid #BEBEBE;
border-radius: 3px;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<button data-activate-modal='#errorModal'>
Open Error Modal
</button>
<button data-activate-modal='#warningModal'>
Open Warning Modal
</button>
<button data-activate-modal='#errorModal'>
Open Error Modal
</button>
<button data-activate-modal='#warningModal'>
Open Warning Modal
</button>
<div
data-component-modal
data-deactivation-duration='500'
id="errorModal"
class="error"
>
<div class="content">
<h1>Error</h1>
<p id="modalContent">
You do not seem to have an Internet connection.
Please check your connection.
</p>
<button data-trigger-inactive>
OK
</button>
</div>
<div class="background"></div>
</div>
<div
data-component-modal
data-deactivation-duration='500'
data-deactivation-delay='500'
id="warningModal"
>
<div class="content">
<h1>Warning</h1>
<p id="modalContent">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<button data-trigger-inactive>
OK
</button>
<button data-trigger-inactive>
close
</button>
</div>
<div class="background"></div>
</div>