Search code examples
javascripthtmlcsscss-transitions

Transition not working correctly when element is added to the DOM


I'm trying to fade in a toast message when i fire a function in JavaScript.

I want the function to create the element, add it to the dom, then fade it in using a css transition, then fade it out using that same transition, then remove it from the dom.

The fade out won't work unless i wrap it in a timeout.

EDIT :: Rickard
I added a button to show the toast.

function flashToast(msg, duration) {
  duration = duration || 3000;

  // create the toast element
  const toastElement = document.createElement("p");
  toastElement.innerHTML = msg;

  toastElement.classList.add("toast");

  // add it to the dom

  document.body.appendChild(toastElement);

  // fade in won't work unless I wrap it in a timeout
  setTimeout(function() {
    toastElement.style.opacity = "1";
  }, 1);

  // remove it after the duration is over
  setTimeout(function() {
    toastElement.style.opacity = "0";
  }, duration - 500);

  // start fading it out 500ms before removing it
  setTimeout(function() {

    document.body.removeChild(toastElement);
  }, duration);
}
.toast {
  display: inline-block;
  font-size: 1.2rem;
  padding: 0.8em 1em;
  border-radius: 5rem;
  color: #eaeaea;
  background: #606060;
  background: rgba(96, 96, 96, 0.7);
  position: absolute;
  bottom: 2%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* opacity is 0 when first enjected */
  opacity: 0;
  transition: opacity 500ms ease;
}
<button onclick="flashToast('Toast!')">Show toast</button>


Solution

  • You having to use a setTimeout is because of how javascript works with the event loop. Here is a really good video explaining a basic fundamental thing that you need to know about javascript. (Hey, I've been working with web development for five years, but been using javascript for 20, and just got to know about this this summer.)

    Jake Archibald: In The Loop

    If you don't want to use a timeout, you can instead use an animation. The downside is that it's hard to control specific times. If you start with opacity 0 and then have opacity 1 at 15%, that will create slower fade ins for longer toast durations.

    function flashToast(msg, duration) {
      duration = duration || 3000;
    
      const toastElement = document.createElement("p");
      toastElement.innerHTML = msg;
    
      toastElement.classList.add("toast");
      
      // Added 
      toastElement.style.setProperty("--duration", duration + "ms");
    
      document.body.appendChild(toastElement);
    
      setTimeout(function() {
        document.body.removeChild(toastElement);
      }, duration);
    }
    .toast {
      display: inline-block;
      font-size: 1.2rem;
      padding: 0.8em 1em;
      border-radius: 5rem;
      color: #eaeaea;
      background: #606060;
      background: rgba(96, 96, 96, 0.7);
      position: absolute;
      bottom: 2%;
      left: 50%;
      transform: translate(-50%, -50%);
      
      /* NEW */
      animation: fade var(--duration) linear;
    }
    
    @keyframes fade {
      0%   {opacity: 0;}
      15%  {opacity: 1;}
      85%  {opacity: 1;}
      100% {opacity: 0;}
    }
    <button onclick="flashToast('Toast!')">Show toast</button>