Skip to content

Instantly share code, notes, and snippets.

@gribnoysup
Last active April 9, 2021 02:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gribnoysup/137c5d80d49a124fea7c49c1dc23a79d to your computer and use it in GitHub Desktop.
Save gribnoysup/137c5d80d49a124fea7c49c1dc23a79d to your computer and use it in GitHub Desktop.
Trigger transition on dynamically appended DOM element

HTML

<p>
  <button class="show">show</button>&nbsp;<button class="hide">hide</button>
</p>
<p class="animation-container"></p>

CSS

body {
  vertical-align: center;
  font-family: Arial;
  margin: 20px;
}

.animate-me {
  display: inline-block;
  padding: 20px;
  transform: scale(1) rotate(0deg);
  border-radius: 3px;
  font-weight: 600;
  color: #FFF;
  background-color: #388E3C;
  transition: .16s ease transform;
}

.animate-me:hover {
  transform: scale(1.05);
  
}

/* Initial enter css styles (from) */
.animate-me.enter {
  transition: .3s ease-in all;
  background-color: #FFECB3;
  opacity: 0;
}

/* Final enter css styles (to) */
.animate-me.enter.enter-active {
  background-color: #388E3C;
  opacity: 1;
}

/* Initial leave css styles (from) */
.animate-me.leave {
  transition: 1.2s ease-out all;
  transform: scale(1) rotate(0deg);
  opacity: 1;
}

/* Final leave css styles (to) */
.animate-me.leave.leave-active {
  transform: scale(0) rotate(720deg);
  opacity:0;
}

JS

const div = document.createElement('span')
const container = document.querySelector('.animation-container')

let isAnimating = false

div.innerText = 'hide me!'
div.classList.add('animate-me')
div.__container = container

function onAppend(el) {
  isAnimating = true
  el.__container.appendChild(el)
  
  // Add initial class
  el.classList.add('enter')
  
  // Trigger sync reflow
  // https://gist.github.com/paulirish/5d52fb081b3570c81e3a
  el.getClientRects()
  
  // Add final css state class
  el.classList.add('enter-active')
  
  // We will remove those classes on the end of transition
  el.addEventListener('transitionend', onTransitionEnd)
}

function onRemove(el) {
  isAnimating = true
  
  // Same as onAppend
  el.classList.add('leave')
  el.getClientRects()
  el.classList.add('leave-active')
  el.addEventListener('transitionend', onTransitionEnd)
}

function show() {
  if (!isAnimating && div.parentNode === null)
    onAppend(div)
}

function hide() {
  if (!isAnimating && div.parentNode !== null)
    onRemove(div)
}

function onTransitionEnd(event) {
  const el = event.target
  const wasVisible = el.classList.contains('enter')
  
  if (wasVisible) {
    el.classList.remove('enter')
    el.classList.remove('enter-active')
  } else {
    el.classList.remove('leave')
    el.classList.remove('leave-active')
    el.parentNode.removeChild(el)
  }
  
  isAnimating = false
  el.removeEventListener('transitionend', onTransitionEnd)
}

document.querySelector('.show').addEventListener('click', show)
document.querySelector('.hide').addEventListener('click', hide)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment