How to fade in a HTML5 dialog?
Asked Answered
S

7

21

How can I fade in an HTML5 dialog? And by dialog I mean HTML5 <dialog> tag (http://demo.agektmr.com/dialog/).

I tried the following (http://jsfiddle.net/v6tbW/) but for some reason the transition does not work.

HTML

<dialog id="myDialog">
    Test
</dialog>

<script>
     document.getElementById('myDialog').show(); // note that this is a method of <dialog>, this is not a jQuery method.
</script>

CSS

dialog {
    position: absolute;
    left: 0; right: 0;
    margin: auto;
    border: solid;
    padding: 1em;
    background: white;
    color: black;

    width: -moz-fit-content;
    width: -webkit-fit-content;
    width: fit-content;

    height: -moz-fit-content;
    height: -webkit-fit-content;
    height: fit-content;

    visibility:hidden;
    opacity:0;
    transition:visibility 10s linear 10s,opacity 10s linear;
}

dialog[open] {   
    visibility:visible;
    opacity:1;
    transition-delay:0s;
}

.backdrop {
    position: fixed;
    background: rgba(0,0,0,0.1);
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}
Strenuous answered 28/7, 2014 at 8:20 Comment(5)
Use vendor specific prefixes for your transition property as well e.g. -webkit-transition:visibility linear 10s, opacity linear 10s;.Balcony
Also, I not aware of any HTMLElement instance that has a show() method. I think you mean to set the open attribute like element.setAttribute('open', 'open')Balcony
Bart: I updated the question. I'm talking about <dialog> element that has .show() method.Strenuous
For simple fadein/out effects it's best to just use css3 transitions as in my example below. Note that this is not supported in IE8 and belowOuphe
To achieve the fade-in effect, use setTimeout to delay setting the opacity property to 1. This slight delay allows the browser to apply the show() method before starting the transition.Paunchy
M
13

Minimal HTML 5 version

The example below has the benefit of no dependencies or external script needed. The <dialog> tag is handy when opened with showModal as it displays a backdrop over the top of DOM declared around it even with display: relative | absolute on its direct parent.

dialog {
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
dialog[open] {
  opacity: 1;
  pointer-events: inherit;
}
dialog::backdrop {
  background-color: rgba(0,0,255, 0.2);
}
<button onclick="dialog.showModal()">show dialog</button>
<dialog id="dialog">
  <p>hi i'm a dialog!</p>
  <form method="dialog">
    <button>Close</button>
  </form>
</dialog>

Using a <form> with method=dialog accomplishes closing the modal without having to handle the close event.

These two references are most enlightening:

https://css-tricks.com/some-hands-on-with-the-html-dialog-element/

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog

Closing points

  • As of 11-6-2020, Safari in general does not support this
  • This makes React portals obsolete for modal purposes
Milson answered 6/11, 2020 at 2:53 Comment(6)
Just to note, the user agent stylesheet in Chromium as of today applies display:none to dialog which on its own seems to defeat the fade in. This snippet works because of the display:flex. I had to set something like dialog { display:block } in order for the fade effect to work.Philbo
revert is a more semantic choice for the pointer-events value on the opened dialog, instead of inherit.Trixy
@Philbo that's a bad approach since the dialog responds to keyboard events, check my answer https://mcmap.net/q/604166/-how-to-fade-in-a-html5-dialogGrogram
@AlexanderNenashev good point. But, I don't think an element with display:none responds to keyboard focus. Your answer shows an error condition in a different way, with dialog { display:flex; opacity: 0 } which leaves it accessible but invisible. Your final fix is good, though :) I'm too busy to investigate much right now.Philbo
@Philbo display:block does the same. any value different than display:none will trigger keyboard eventsGrogram
@AlexanderNenashev I see what was confusing me now, my test dialog is actually stacked on top of another dialog, so when I closed it leaving it with display:block it was removed from the top-layer but was still blocked from keyboard by the remaining dialog being in top-layer :) My original comment was to help people simply using opacity and wondering why no fade at all happened, but your approaches are much better.Philbo
U
6

You can transition the element if you set display: block on it (and allow time for this style to be applied to the element).

Demo: http://jsfiddle.net/v6tbW/11/

To do this with .showModal(), unfortunately it appears that transitions don't work with only the [open] attribute. They do appear to work if you add another class though:

http://jsfiddle.net/karlhorky/eg4n3x18/

Urea answered 27/3, 2015 at 14:11 Comment(0)
D
6

You can consider using

dialog[open] {
    animation: myFadeIn 5.0s ease normal;
  }
  
@keyframes myFadeIn{
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

Eample

<style>
  /* 👇 Optional. change the background style. */
  dialog::backdrop {
    background-color: rgba(255, 128, 30, .75);
    backdrop-filter: blur(3px);
  }

  /* 👇 style1: fadeIn */
  dialog[open] {
    animation: myFadeIn 5.0s ease normal;
  }
  @keyframes myFadeIn{
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  /* 👇 style2: top2center */
  dialog#top2center[open] {
    // Find your favorite style from here: https://cubic-bezier.com/
    // animation: myTop2Center 3.0s ease normal;
    animation: myTop2Center 1.2s cubic-bezier(.33,1.44,.83,.22)
  }
  @keyframes myTop2Center{
    from {
      transform: translateY(-200%);
    }
    to {
      transform: translateY(0%);
    }
  }
</style>

<dialog>
  <header>FadeIn</header>
  <form>
    <button>Close</button>
  </form>
</dialog>

<dialog id="top2center">
  <header>Top2Center</header>
  <form>
    <button>Close</button>
  </form>
</dialog>

<script>
  document.querySelectorAll(`dialog`).forEach(dialogElem=>{
    const testName = dialogElem.querySelector(`header`).innerText
    const frag = document.createRange().createContextualFragment(`<button>${testName}</button><br>`)
    const showBtn = frag.querySelector(`button`)
    const closeBtn = dialogElem.querySelector(`button`)
    showBtn.onclick = () => dialogElem.showModal()
    closeBtn.onclick = () => dialogElem.close()
    dialogElem.querySelector(`form`).onsubmit = () => false // To stop submit event.
    document.body.append(frag)
  })
</script>
Delois answered 1/11, 2021 at 9:2 Comment(1)
Those -webkit- prefixes are needed any more. IIRC it worked for quite few years without them.Afloat
R
3

Fade in and out example

This example uses a combination of CSS animation and the Javascript Web Animations API to achieve a fade in and fade out without disrupting the intended display properties of the dialog.

On the fade in, use a CSS animation to give the dialog opacity and translate, or whichever effect is desired, by applying the animation to the dialog[open] selector.

On close, create a new Animation and reverse the original animation with a KeyFrameEffect. Play the animation and listen for the finish event, and call the modal close function inside the finish event handler.

const dialog = document.querySelector("dialog")
const open = document.querySelector("#open")
const close = document.querySelector("#close")

open.addEventListener("click", () => dialog.showModal())
close.addEventListener("click", handleClose)

function handleClose() {
  const keyFrame = new KeyframeEffect(
    dialog, 
    [{ translate: "0 -100%", opacity: "0" }], 
    { duration: 500, easing: "ease", direction: "normal" }
  )

  const animation = new Animation(keyFrame, document.timeline)
  animation.play()
  animation.onfinish = () => dialog.close()
}
dialog[open] {
  animation: modal-in 500ms forwards ease;
}

@keyframes modal-in {
  from {
    translate: 0 -100%;
    opacity: 0;
  }
}

dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.2)
}
<button id="open">Open dialog</button>

<dialog>
  <p>Animated open and close</p>
  <button id="close">Close dialog</button>
</dialog>

Side note

I was unable to transition or animate the ::backdrop in any way. That is the last piece I was unable to solve.

Randers answered 12/11, 2023 at 8:37 Comment(0)
G
2

First I've tried King Friday's solution and found it faulty. The problem that if you change a closed dialog's default display:none you are actually just hide it and it responds to keyboard events:

dialog {
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
dialog[open] {
  opacity: 1;
  pointer-events: inherit;
}
dialog::backdrop {
  background-color: rgba(0,0,255, 0.2);
}
Press TAB, the focus will be on the show dialog button
<br>
Then press TAB again, the focus will be on the dialog close button, and its `keyup` listener will be triggered
<br>

<button id="$open" onclick="dialog.showModal()">show dialog</button>
<dialog id="dialog">
  <p>hi i'm a dialog!</p>
  <form method="dialog">
    <button onkeyup="alert('OOPS, the hidden dialog works!')">Close</button>
  </form>
</dialog>
<script>setTimeout(()=>$open.focus(),100)</script>

So to my answer:

A dialog transitions from display:none to display:block on the opening and that ruins transition. The idea was to add dialog { display:block; } but it turned out bad because the dialog is just hidden and reacts to the keyboard TAB navigation for example... So we need to keep a closed dialog display:none.

So a couple of solutions:

  1. You could use animation which I like since it's a pure CSS solution:
dialog[open] {
  animation: fadein 2s ease-in forwards;
}

@keyframes fadein{
  0%{
    opacity:0;
  }
  100%{
    opacity:1;
    background-color: green;
  }
}

const dialog = document.querySelector("dialog");

document.querySelector("#open-button").addEventListener("click", () => {
  dialog.showModal();
});
document.querySelector("#close-button").addEventListener("click", () => {
  dialog.close();
});
button {
  display: block;
}

dialog {
  position: absolute;
  top: 50px;
  margin: auto;
  padding: 0;
  width: 50%;
  height: 50%;
  background-color: red;
  opacity: 0;
}

dialog[open] {
  animation: fadein 2s ease-in forwards;
}

@keyframes fadein{
  0%{
    opacity:0;
  }
  100%{
    opacity:1;
    background-color: green;
  }
}
<button id="open-button">Open Dialog Element</button>

<dialog>
  <button id="close-button">Close Dialog Element</button>
</dialog>
  1. You could also add a CSS class after the opening with setTimeout to allow the DOM to be re-rendered and remove it on the closing with your transition left intact.
setTimeout(()=>dialog.classList.add('open'));
dialog.addEventListener('close', () => dialog.classList.remove('open'));

const dialog = document.querySelector("dialog");

dialog.addEventListener('close', () => dialog.classList.remove('open'));

document.querySelector("#open-button").addEventListener("click", () => {
  dialog.showModal();
  setTimeout(()=>dialog.classList.add('open'));
});

document.querySelector("#close-button").addEventListener("click", () => {
  dialog.close();
});
button {
  display: block;
}

dialog {
  position: absolute;
  top: 50px;
  margin: auto;
  padding: 0;
  width: 50%;
  height: 50%;
  background-color: red;
  opacity: 0;
  -webkit-transition: opacity 2s ease-in, background-color 2s ease-in;
  -o-transition: opacity 2s ease-in, background-color 2s ease-in;
  transition: opacity 2s ease-in, background-color 2s ease-in;
}

dialog.open {
  background-color: green;
  opacity: 1;
}
<button id="open-button">Open Dialog Element</button>

<dialog>
  <button id="close-button">Close Dialog Element</button>
</dialog>
Grogram answered 17/7, 2023 at 23:25 Comment(1)
thanks @alexander-nenashev I did not see this until recently. You bring up a good point and a good solution. I've been using chatgpt so much, i don't look here anymore. :)Milson
A
-1

Since you're using jQuery. This is an easier approch:

http://jsfiddle.net/v6tbW/3/

HTML

<dialog id="myDialog">
    Test
</dialog>

CSS

dialog {
    display: none;
    position: absolute;
    left: 0; right: 0;
    margin: auto;
    border: solid;
    padding: 1em;
    background: white;
    color: black;

    width: -moz-fit-content;
    width: -webkit-fit-content;
    width: fit-content;

    height: -moz-fit-content;
    height: -webkit-fit-content;
    height: fit-content;
}

jQuery

$(function() {

    $('#myDialog').fadeIn(10000);

});
Apiculture answered 28/7, 2014 at 8:25 Comment(7)
Actually, if you keep the transition (prefixed!) in CSS and then use $('#myDialog').css(opacity,1); it will accomplish the same effect, but hardware accelerated with CSS3. Now you can even leave jQuery out of the picture and do document.getElementById('myDialog').style.opacity = 1; and it will still work.Mattison
@Mattison Cool approach! But since he's already using jQuery and may need it for other stuff, this was the easiest solution I could think off.Apiculture
Niklas: Yes, I know about fadeIn() method. I would like to use the native method dialog.show() and CSS3 transitions. jQuery should not be necessary.Strenuous
@Strenuous I'm not sure about the show() method. I'm also pretty sure most browsers would't support this as you're really living on the cutting edge with no fallbacks for even an outdated version of a non-IE browser. I'd even recommend not using these semantically special elements right now, so most of your stuff would work way back with only minor adjustments... Edit According to developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog the method you're looking for is showModal.Mattison
@somethinghere: It is a new feature that was released in Chrome 36: chromestatus.com/features/5770237022568448; demo with API: demo.agektmr.com/dialogStrenuous
@Strenuous Actually, according to both the articles (what you posted and the one I found on MDN) the method is showModal() and not show(). Did that help (Also, interesting stuff, the cutting edge :) )Mattison
somethinghere: Actually no, show() and showModal() behave differently. show() displays a window that is not modal.Strenuous
O
-2

Here's a working example of using css transition that you started with and proper jquery selector, that adds the "no-ninja" class to your DIV, on window load event:

http://jsfiddle.net/v6tbW/6/

html:

<dialog id="myDialog">
 Test
</dialog>

css:

dialog {
 position: absolute;
 left: 0; right: 0;
 margin: auto;
 border: solid;
 padding: 1em;
 background: red;
 color: black;
 dispaly:block;

 width: -moz-fit-content;
 width: -webkit-fit-content;
 width: fit-content;

 height: -moz-fit-content;
 height: -webkit-fit-content;
 height: fit-content;

 /*visibility:hidden;*/
 opacity:0;
 -webkit-transition: opacity 10s linear;
}

dialog[open] {   
 visibility:visible;
 opacity:1;
 transition-delay:0s;
} 

.backdrop {
 position: fixed;
 background: rgba(0,0,0,0.1);
 top: 0;
 bottom: 0;
 left: 0;
 right: 0;
}

.no-ninja{
  opacity:1;
}

js:

$("#myDialog").addClass('no-ninja');
Ouphe answered 28/7, 2014 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.