Skip to content

Instantly share code, notes, and snippets.

@elycruz
Last active February 18, 2024 01:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elycruz/4fa4ee1e346ceb415dde7d98fd4200e8 to your computer and use it in GitHub Desktop.
Save elycruz/4fa4ee1e346ceb415dde7d98fd4200e8 to your computer and use it in GitHub Desktop.
Modal dialog '::backdrop' replacement.
/**
* Custom modal "backdrop" animation replacement, until `::backdrop` animations are supported.
*/
document.body.innerHTML = `
<style>
body {
display: grid;
place-content: center;
}
:root {
--modal-bg-speed: 0.21s;
--dialog-speed: 0.5s;
}
dialog {
position: fixed;
display: grid;
min-width: 233px;
max-width: 987px;
margin: auto;
inset: 0;
}
/**
* "Slide-out" state.
*/
dialog:not([open], [inert]) {
animation:
var(--dialog-speed) slide-out-to-bottom,
var(--dialog-speed) fade-out
;
transform: translateY(100%);
opacity: 0;
}
/**
* Ensure we don't "offscreen" the dialog until "out" animation is done/we add \`inert\` prop.
*/
dialog:not([inert]) {
z-index: 9999;
}
/**
* "Slide-in" state.
*/
dialog[open] {
animation:
var(--dialog-speed) slide-in-from-bottom,
var(--dialog-speed) fade-in;
animation-delay: var(--modal-bg-speed);
animation-fill-mode: backwards;
transform: translateY(0);
opacity: 1;
}
.dialog-header {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
/**
* Custom modal backdrop.
*/
dialog::backdrop {
background: rgba(0, 0, 0, 0);
}
.modal-backdrop {
position: fixed;
background: rgba(0,0,0,0.25);
inset: 0;
user-select: none;
animation: var(--modal-bg-speed) fade-out;
animation-fill-mode: forwards;
animation-delay: var(--dialog-speed);
pointer-events: none;
}
dialog[open] + .modal-backdrop {
animation: var(--modal-bg-speed) fade-in;
z-index: 9998;
}
/**
* Inert/offscreen state - Added (by us) on page load, and on 'close' animation end.
*/
:where(dialog, .modal-backdrop):is([inert]) {
display: none;
pointer-events: none;
z-index: -1;
}
/**
* Keyframes.
*/
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes slide-in-from-bottom {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes slide-out-to-bottom {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
</style>
<dialog inert>
<header class="dialog-header">
<span>Title</span>
<button type="button" class="close-btn">x</button>
</header>
<section>
<p>Content.</p>
</section>
</dialog>
<div class="modal-backdrop" aria-hidden="true" inert></div>
<button type="button" class="open-btn">Open</button>
`;
const $ = (sel, root = document) => root.querySelector(sel),
dialog = $('dialog'),
modalBg = $('.modal-backdrop');
$('.close-btn').addEventListener('click', () => {
dialog.close();
});
$('.open-btn').addEventListener('click', () => {
if (dialog.inert) {
modalBg.hidden =
modalBg.inert =
dialog.hidden =
dialog.inert = false;
}
dialog.showModal();
});
dialog.addEventListener('mouseup', (e) => {
const box = dialog.getBoundingClientRect();
if (e.clientX < box.left ||
e.clientX > box.right ||
e.clientY < box.top ||
e.clientY > box.bottom
) {
e.preventDefault();
dialog.close();
}
});
dialog.addEventListener('close', e => {
modalBg.addEventListener('animationend', () => {
modalBg.hidden =
modalBg.inert =
dialog.hidden =
dialog.inert = true;
}, {once: true});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment