Rounin
Rounin

Reputation: 29453

How to animate CSS ::backdrop behind HTML <dialog>?

The <dialog> element is now cross-browser compatible (since March 2022).

I tried my hand at it today and familiarised myself with:

Everything seems straightforward but the one thing I've been unable to achieve so far is: fading up the backdrop.

For instance, if I want the final color of the backdrop to be:

but have it transition (or animate) to that background-color from:

is this even possible?


Here is my (non-working) example:

const myButton = document.querySelector('button');
const myDialog = document.querySelector('dialog');

const requestDialog = () => {
  myDialog.showModal();
  setTimeout(() => myDialog.classList.add('fadeUp'), 400);
}

myButton.addEventListener('click', requestDialog, false);
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 180px;
}

button {
  position: absolute;
  top: 6px;
  left: 6px;
  cursor: pointer;
}

dialog::backdrop {
  background-color: rgba(0, 0, 0, 0);
  transition: backgroundColor 0.6s ease-out;
}

dialog.fadeUp::backdrop {
  background-color: rgba(0, 0, 63, 0.8);
}
<button type="button">Click me to<br />Request Dialog</button>

<dialog>
  <h2>My Dialog</h2>
</dialog>

Upvotes: 13

Views: 9804

Answers (4)

Dituon
Dituon

Reputation: 51

So I wrote this function, it works fine in my chrome

function getDialog(selector) {
    const dialog = selector ? document.querySelector(selector) : document.createElement('dialog')
    const oldShow = dialog.showModal
    dialog.showModal = () => {
        oldShow.call(dialog)
        dialog.classList.add('show')
    }
    const oldClose = dialog.close
    dialog.close = () => {
        dialog.addEventListener('transitionend', () => oldClose.call(dialog), {once: true})
        dialog.classList.remove('show')
    }
    return dialog
}

const dialog = getDialog('dialog')

const showBtn = document.getElementById('show-btn')
showBtn.addEventListener('click', () => dialog.showModal())

const closeBtn = document.getElementById('close-btn')
closeBtn.addEventListener('click', () => dialog.close())
dialog {
    opacity: 0;
    transition: opacity 0.5s ease;
}

dialog.show {
    opacity: 1;
}

dialog::backdrop {
    background-color: #00000000;
    transition: background-color 0.5s ease;
}

dialog.show::backdrop {
    background-color: #00000088;
}
<button id="show-btn">show</button>
<dialog>
  <p>hello :)</p>
  <button id="close-btn">close</button>
</dialog>

--- edit ---

Thanks @Destroy666 for the reminder, this is my first answer on stackoverflow, I still have a lot to do, The following is additional information:

Compared to @G-Cyrillus and @YoshiJL 's answer:

  • I used the ::backdrop selector instead of box-shadow alternative, which is more intuitive.

Compared to @Adriano 's answer:

  • I use transition instead of @keyframes animation, which avoids the problem of not playing the complete animation caused by closing the dialog before the animation ends.

  • Also works fine on Firefox.

In addition:

  • I encapsulated a function to create animated dialog, and only need to pass in the html element selector, dont need to change the existing code.

Upvotes: 5

YoshiJL
YoshiJL

Reputation: 31

G-Cyrillus's answer is almost perfect! Just added the use of max to the box-shadow so that the shadow will cover on mobile devices.

box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0);

https://stackoverflow.com/a/71764440/19130936

Upvotes: 3

G-Cyrillus
G-Cyrillus

Reputation: 105863

If you use a box-shadow, it might work and be close enough to what you try to do :

dialog {
   box-shadow: 0 0 0 100vw rgba(0, 0, 0, 0);
   transition:  2s ease-out;
 }  
dialog.fadeUp {
   box-shadow: 0 0 0 100vw  rgba(0, 0, 63, 0.8);
   transition:  2s ease-out;
 }

Upvotes: 6

Adriano
Adriano

Reputation: 71

I was having the same problem but this solved to me.

const dialog = document.querySelector('.modal')

function openModal() {
  dialog.showModal(); // default dialog method
}

function closeModal() {
  dialog.classList.add('close'); // run animation here
  
  dialog.addEventListener('animationend', () => {
    dialog.classList.remove('close')
    dialog.close(); // then run the default close method
  }, {once : true}); // add this to prevent bugs when reopening the modal
}
.modal {
  display: none;
}
/* when the dialog is open by its default method it adds a open tag on the element */
.modal[open] {
  display: flex;
}

.modal[open]::backdrop {
  animation: backdrop-fade 2s  ease forwards;
}

.modal.close::backdrop {
  animation: backdrop-fade 3s ease backwards;
  animation-direction: reverse;
}

@keyframes backdrop-fade {
  from {
    background: transparent;
  }
  to{
    background: rgba(0,0,0);
  }
}
<button onclick="openModal()">open modal</button>
<dialog class="modal">
    <button onclick="closeModal()">close modal</button>
</dialog>

Upvotes: 7

Related Questions