- How to animate an element in display none in two steps
- The solution
- In this article:
- Problem we try to solve
- Step 1: animate during opening
- JS to show the modal
- CSS
- Step 2 : animate during closing
- JS
- CSS
- Animationend event
- Bonus : A little accessibility enhancement
- Button vs Link
- Don't play animation if user don't want animation.
- Анимация перехода display из none в block
- Пример
- Проблема
- Решение
- Решение с обратной анимацией
- doctor Brain
- Анимация элементов с display:none и callback функции
- CSS переходы (transition)
- Проблема 1
- Как использовать callback-функции после transition?
- Проблема 2
- Как сделать анимацию для элемента с исходным свойством display:none
- Метод Reflow
- Новые публикации
- JavaScript: сохраняем страницу в pdf
- HTML: Полезные примеры
- CSS: Ускоряем загрузку страницы
- JavaScript: 5 странностей
- JavaScript: конструктор сортировщиков
- Категории
- О нас
- So, you’d like to animate the display property
How to animate an element in display none in two steps
Animate a hidden element is very simple now. We just need 2 CSS declaration and a bit of JavaScript to toggle the state open / close.
The solution
In this article:
- CSS :not pseudo-selector
- Single Source of truth
- animationend event
- Accessibility
Problem we try to solve
You have a hidden element with a hidden attribute. To provide a better UX, you want to animate this opening and closing state. But in CSS, display:none is like an interrupter; it can be «on» or «off» but animate between both state with a CSS transition is impossible.
Step 1: animate during opening
As you notice, I’m opting for a link instead of a button. Why ? Because my modal will still be accessible without JavaScript, thanks to the target CSS pseudo-class. I will focus on this point after.
JS to show the modal
modalButton.addEventListener("click", function (e) < e.preventDefault(); modal.hidden = false; >);
CSS
That’s it. ¯_(ツ)_/¯.
If you prefer relying on .hidden class (like in Tailwind), you can switch :not([hidden]) with :not(.hidden) . If you want both, the not pseudo-class accept multiple arguments separated by a comma : not([hidden], .hidden) . Anyway, our Modal appears with a shiny animation now :
Step 2 : animate during closing
The closing state is a little more tricky. If you set the hidden attribute to «true», you won’t be able to hide it smoothly. You need to add a temporary class like is-closing to play the closing animation and then, hide the element.
JS
modal.addEventListener("click", function (e) < // Omitted… if (hasClickedOutside || hasClickedCloseButton) < modal.classList.add("is-closing"); // Omitted…
CSS
Now our modal is closing smoothly, but it is not back to hidden state. You have to wait to the end of the animation to remove the .is-closing class and back to hidden="true" . With setTimeout ? You could, but you have a better option.
Animationend event
With a timeout , we have to declare a value at least equal to the animation duration, which can change.
If you can, you have to have a single source of truth : here, the animation duration declared in the CSS.
The animationend will wait to the end of the animation, then execute the function inside the listener.
modal.addEventListener("click", function (e) < const hasClickedOutside = !e.target.closest(".modal-main"); const hasClickedCloseButton = e.target.closest(".modal-close"); if (hasClickedOutside || hasClickedCloseButton) < modal.classList.add("is-closing"); modal.addEventListener( "animationend", function () < modal.hidden = true; modal.classList.remove("is-closing"); >, < once: true >); > >);
Once the event is completely done, you have to destroy it with the once: true option, as you don't need it anymore.
And voilà, you have the knowledge to animate any element hidden in the DOM.
Bonus : A little accessibility enhancement
Button vs Link
Without JS, the modal can still be open via its hash, and you can style the opened state with the :target pseudo class.
To close it, the user needs to back in your history. This is why I hide the .modal-close . It's not pertinent to show it if it can't do anything.
Don't play animation if user don't want animation.
For personal taste, medical reason or to solve a performance issue on their device, your users may not want any animation, and you have to respect their preferences. It would be a good idea to embed the following the rule as part of your CSS reset, if it's not already done.
@media (prefers-reduced-motion: reduce) < *, *::before, *::after < animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; >>
Анимация перехода display из none в block
Есть некий объект со свойством display: none . Нужно анимировать его появление, при изменении параметра display , например, на block .
Пример
Невидимый прямоугольник делаем видимым при нажатии на кнопку:
document.addEventListener("DOMContentLoaded", () => const button = document.getElementById("button"); const rect = document.getElementById("rect"); button.addEventListener("click", () => rect.classList.toggle("is-visible"); >); >);
#rect width: 100px; height: 100px; background-color: red; display: none; > #rect.is-visible display: block; >
Проблема
Стандартный способ анимации через transition не работает. Но это и не удивительно:
#rect width: 100px; height: 100px; background-color: red; transition: all 0.5s ease-in-out; display: none; >
Но даже через введение параметра opacity ничего не работает:
#rect width: 100px; height: 100px; background-color: red; transition: all 0.5s ease-in-out; display: none; opacity: 0; > #rect.is-visible display: block; opacity: 1; >
Решение
Будем использовать @keyframes :
#rect width: 100px; height: 100px; background-color: red; display: none; opacity: 0; > #rect.is-visible display: block; opacity: 1; animation: fadeInFromNone 3s ease-in-out; > @keyframes fadeInFromNone 0% display: none; opacity: 0; > 1% display: block; opacity: 0; > 100% display: block; opacity: 1; > >
Решение с обратной анимацией
Сейчас анимирован только переход из невидимого состояния в видимое. Сделаем так, чтобы работало и в обратную сторону. Как оказалось, это гораздо сложнее.
#rect width: 100px; height: 100px; background-color: red; display: none; opacity: 0; > #rect.is-visible display: block; animation: fadeInFromNone 0.5s ease-in-out; animation-fill-mode: forwards; > #rect.is-hidden animation: fadeOutFromBlock 0.5s ease-in-out; > @keyframes fadeInFromNone 0% opacity: 0; > 100% opacity: 1; > > @keyframes fadeOutFromBlock 0% opacity: 1; > 100% opacity: 0; > >
document.addEventListener("DOMContentLoaded", () => const button = document.getElementById("button"); const rect = document.getElementById("rect"); button.addEventListener("click", () => toggleTwoClasses(rect, "is-visible", "is-hidden", 500); >); >); function toggleTwoClasses(element, first, second, timeOfAnimation) if (!element.classList.contains(first)) element.classList.add(first); element.classList.remove(second); > else element.classList.add(second); window.setTimeout(function() element.classList.remove(first); >, timeOfAnimation); > >
Если добавим в HTML первый класс is visible , то вначале элемент будет видимым, а потом переключаться с видимого на невидимый:
id="button">Show id="rect" class="is-visible">