Modal dialog animation

Easily create seamless transitions between a clickable element and its expanded version inside a modal.
You can even animate from elements outside of the specified root by using a combination of layout ids and specifying children.

Modal dialog animation code example

import { createLayout, utils } from 'animejs';

const buttons = utils.$('button');

// Create demo dialog and append it to the body
const $dialog = document.createElement('dialog');
$dialog.id = 'layout-dialog';
document.body.appendChild($dialog);

// Create the modal layout by setting the dialog as the root
// Since the elements are not yet part of the modal root, it's necessary to specify all animated children
// to tell the layout what to look for during an update
const modalLayout = createLayout($dialog, {
  children: ['.item', 'h2', 'h3', 'p'],
  properties: ['--overlay-alpha'],
});

const closeModal = (e) => {
  let $item;
  modalLayout.update(({ root }) => {
    $dialog.close();
    $item = buttons.find(item => item.classList.contains('is-open'));
    $item.classList.remove('is-open'); // Makes the clicked element visible again
    $item.focus(); // Focus to the closed element to preserve the keyboard navigation flow
  });
};

const openModal = e => {
  const $target = e.target;
  const $item = $target.closest('.item');
  const $clone = $item.cloneNode(true);
  $dialog.innerHTML = ''; // Make sure old clones are removed from the modal before adding a new one
  $dialog.appendChild($clone); // Append the clicked element clone to the modal
  modalLayout.update(() => {
    $dialog.showModal(); // Open the modal
    $item.classList.add('is-open'); // Hide the clicked element
  }, {
    duration: $item.dataset.duration // Custom duration depending of the button clicked
  });
}

buttons.forEach($button => $button.addEventListener('click', openModal));
$dialog.addEventListener('cancel', closeModal);
$dialog.addEventListener('click', closeModal);
<div class="large layout centered row">
  <div class="layout-container col grid-layout row">
    <button data-layout-id="A" data-duration="500" class="button item col">
      <h2 data-layout-id="A-title">Item A</h2>
      <h3 data-layout-id="A-duration">(500ms)</h3>
      <p data-layout-id="A-description">This p tag is hidden by default and only visible when appended inside the dialog element. Its position and opacity are automatically animated.</p>
    </button>
    <button data-layout-id="B" data-duration="1000" class="button item col">
      <h2 data-layout-id="B-title">Item B</h2>
      <h3 data-layout-id="B-duration">(1000ms)</h3>
      <p data-layout-id="B-description">This p tag is hidden by default and only visible when appended inside the dialog element. Its position and opacity are automatically animated.</p>
    </button>
    <button data-layout-id="C" data-duration="2000" class="button item col">
      <h2 data-layout-id="C-title">Item C</h2>
      <h3 data-layout-id="B-duration">(2000ms)</h3>
      <p data-layout-id="C-description">This p tag is hidden by default and only visible when appended inside the dialog element. Its position and opacity are automatically animated.</p>
    </button>
  </div>
</div>
#layout-dialog {
  --overlay-alpha: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  z-index: 1000;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100dvh;
  max-width: 100%;
  max-height: 100%;
  border: none;
  background: transparent;
  pointer-events: none;
  background-color: color-mix(in srgb, var(--hex-black-1), transparent var(--overlay-alpha));
}

#layout-dialog[open] {
  --overlay-alpha: 40%;
  pointer-events: auto;
}

#layout-dialog::backdrop {
  background: transparent;
}

#layout-dialog .item {
  position: relative;
  visibility: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
  width: 22rem;
  height: 14.5rem;
  padding: 2rem;
  border-radius: 1rem;
  border: 1px solid var(--hex-green-6);
  font-size: 1rem;
  color: var(--hex-green-6);
  background-color: var(--hex-green-1);
}

#layout-dialog[open] .item {
  visibility: visible;
}

#layout-dialog .item h2 {
  font-size: 2rem;
  margin-bottom: 1rem;
  will-change: font-size;
}

#layout-dialog .item h3 {
  position: absolute;
  top: 2.75rem;
  right: 2rem;
}

#layout-usage-animate-modal-dialog .item {
  cursor: pointer;
}

#layout-usage-animate-modal-dialog .item.is-open {
  visibility: hidden;
}

#layout-usage-animate-modal-dialog .item p {
  text-align: left;
  display: none;
}

#layout-dialog .item p {
  padding-top: 1rem;
  border-top: 1px solid currentColor;  
}

#layout-dialog[open] .item p {
  display: block;
}