Анимация перехода display из none в block

Есть некий объект со свойством display: none . Нужно анимировать его появление, при изменении параметра display , например, на block .
Пример
Невидимый прямоугольник делаем видимым при нажатии на кнопку:
Проблема
Стандартный способ анимации через transition не работает. Но это и не удивительно:
Но даже через введение параметра opacity ничего не работает:
Решение
Будем использовать @keyframes :
Решение с обратной анимацией
Сейчас анимирован только переход из невидимого состояния в видимое. Сделаем так, чтобы работало и в обратную сторону. Как оказалось, это гораздо сложнее.
Если добавим в HTML первый класс is visible , то вначале элемент будет видимым, а потом переключаться с видимого на невидимый:
Анимация от display: block до display: none

Отлично было бы это сделать, но на данный момент это невозможно и, думаю, никогда не станет реальностью (например, как анимировать до “display: table”?). Но есть возможность совершить обходной маневр, и один из них я здесь и опишу.
Зачем анимировать “display”?
Анимировать свойство display требуется для решения следующей проблемы:
Нужно, чтобы элемент постепенно зрительно исчезал со страницы.
Вам нехочется, чтобы этот элемент занимал место после своего исчезновения (например, требуется, чтобы его исчезновение вызывало перезаливку).

Практический курс по верстке адаптивного сайта с нуля!
Изучите курс и узнайте, как верстать современные сайты на HTML5 и CSS3
Нужно применять для анимации CSS, а не библиотеку.
По этой причине недостаточно просто анимировать opacity до нуля, так как элемент с нулевой непрозрачностью занимает то же самое место на странице. Давайте посмотрим, как шаг за шагом решить эту проблему.
Используйте Opacity и Display
Первое, о чем вы можете подумать – воспользоваться и свойством opacity, и display. Наш HTML будет выглядеть так:
Обратите внимание на то, что display: none и opacity: 0 присвоен класс “hidden”. Если переключать класс “hidden” с помощью jQuery, получится примерно такой код:
Но если так сделать, то мы не увидим эффекта перехода (определенного в блоке описаний .box). Вместо него мы увидим это (слегка отличающееся в Firefox по сравнению с Chrome/IE10):
Многократно нажмите кнопку ‘togglevisibility’ и увидите, как блок безо всяких переходов будет неожиданно исчезать и появляться. Чтобы это исправить, можно попробовать отделить в CSS свойство display от opacity:
Затем можно переключить оба класса, один за другим:

Практический курс по верстке адаптивного сайта с нуля!
Изучите курс и узнайте, как верстать современные сайты на HTML5 и CSS3
Помимо того, что требуется реверсировать эти две строки при появлении блока, это еще и не сработает, как видно здесь:
Нам нужно, чтобы элемент визуально исчезал, а затем удалялся со страницы по окончанию исчезновения идентично функции обратного вызова. (Кроме того, даже если мы скомбинируем opacity: 0 с visibility: hidden, то анимация получится хорошо, но элемент после своего исчезновения продолжит занимать место на странице, так что все это не сработает.)
Почему не работает?
До перехода к своему методу я просто объясню, почему невозможно ничего сделать, просто изменяя классы один за другим. Во-первых, если вы добавляете классы как в вышеприведенных примерах, и если переход даже сработал, то вам придется установить отдельный раздел для удаления классов и реверсирования (т.е. если блок сначала скрыт, вам придется установить его на display: block, а затем сменить непрозрачность).
Но это несущественно, потому что все равно не работает. JavaScript выполняет одну строку за другой, но не ждет, пока закончат выполняться все связанные с ней процессы, а переходит к выполнению следующей строки. (Другими словами, если в строке 1 вы выполняете анимацию или другое несинхронное событие, строка 2 будет исполняться, даже если анимация не выполнена).
Это происходит от того, что непрозрачность ‘opacity’ пытается анимироваться сразу же, и даже если на долю миллисекунды она действительно анимируется, вы этого не увидите, потому что display: none начнет действовать столь же быстро.
Можно резюмировать проблему, которую нам нужно решить, следующим образом:
Когда элемент виден, сначала анимируйте непрозрачность, а затем, когда это выполнено, сделайте его display: none.
Когда элемент невидим, сначала сделайте его display: block, затем (пока он еще визуально скрыт, но уже существует на странице), анимируйте непрозрачность.
Возможное решение
Вероятно, существуют другие способы это сделать, и я буду рад узнать, как вы бы решили эту проблему. Но вот мое мнение.
CSS тот же самый (с двумя все еще разделенными классами ‘hidden’), но jQuery будет выглядеть так:
Анимация от «display: block» до «display: none»
Возможно вам известно, что с помощью свойств transition и animation в CSS3 можно создавать анимацию для определенных CSS-свойств . Но есть свойства, не поддающиеся анимации. Одним из них является display .
Было бы очень полезно анимировать это свойство, но это невозможно, как мне казалось до недавнего времени (ну например, как анимировать нечисловое свойство display: table ?). Но есть обходной путь, который я покажу в данной статье.
Зачем может понадобиться анимировать свойство «display»?
Нужда в анимации свойства display может исходить от необходимости решить следующие проблемы:
- Нужно сделать плавное исчезновение объекта со страницы;
- Вы не хотите, чтобы элемент занимал место после своего исчезновения (например, нужно вызвать перестройку потока вывода после исчезновения некоторого объекта);
- Вы хотите использовать для анимации исключительно CSS, без применения сторонних библиотек.
Исходя из вышеперечисленного, изменения значения свойства opacity до нуля недостаточно, так как элемент с нулевой прозрачностью все еще будет занимать место на странице.
Давайте пошагово решим поставленную задачу.
Использование Opacity и Display
Первым делом, мы попробуем использовать оба свойства — и opacity и display . HTML-код будет таким:
CSS-код может выглядеть так:
В классе « hidden » у нас имеются значения display: none и opacity: 0 . Если мы будем подставлять данный класс с помощью jQuery, то можно использовать такой код:
Однако если мы сделаем так, то не увидим эффекта перехода, который определен в стилевом блоке .box . Получится вот что (пример в Firefox немного отличается от Chrome/IE10):
Нажмите несколько раз на кнопку « Переключение видимости » и увидите, что прямоугольник исчезает и появляется мгновенно, без перехода.
Чтобы это исправить, мы можем попробовать отделить свойство display от opacity в CSS-файле:
Теперь мы можем переключать оба класса по очереди:
Однако данный вариант не поможет нам выполнить поставленной задачи:
Нам нужно, чтобы элемент сначала плавно исчезал со страницы визуально, а затем полностью удалялся.
Даже если мы скомбинируем opacity: 0 с visibility: hidden , то несмотря на присутствие анимации, элемент по-прежнему будет занимать место на странице после исчезновения, что опять же нас не устраивает.
Почему не работает?
Перед применением моего метода, я хочу пояснить, почему мы не можем решить задачу простой поочередной заменой классов.
Во-первых, если вы будете добавлять классы так, как показано в примере выше, даже при нормально работающем переходе, необходимо будет разделить секцию для удаления и замены классов (то есть, если прямоугольник начинает скрываться, то сначала нужно установить свойство display: block , а затем изменить прозрачность).
Но этот прием не работает. JavaScript выполняет строку за строкой и не ждет выполнения операций, которые запускает каждая из них. Другими словами, если вы запускаете анимацию или другое асинхронное событие в строке 1, строка 2 будет выполнена, даже если анимация еще не закончилась.
После запуска анимации свойством opacity , а ее выполнение требует определенного времени, вы не увидите процесса, потому что строка display: none выполняется тут же.
Давайте подытожим возникшие перед нами проблемы:
- Когда элемент видим, сначала запускаем анимацию прозрачности, а затем, после её окончания, применяем строку display: none ;
- Когда элемент невидим, сначала устанавливаем свойство display: block , а затем пока он еще невидим, но уже присутствует на странице, запускаем анимацию.
Возможное решение
Возможно, есть и другие способы решения данной проблемы, но мой способ таков.
CSS-код не менется (два класса hidden по-прежнему разделены), а вот код jQuery будет таким:
А вот пример в действии:
Вот что этот код делает, когда прямоугольник видим:
- Добавляет класс visuallyhidden , который запускает анимацию до полного исчезновения элемента;
- После добавления класса, добавляется обработчик с помощью jQuery -метода .one() , который ожидает возникновения события transitionend ;
- Событие transitionend наступает, когда анимация свойства opacity закончилась, и было установлено свойство display: block .
Так как мы не можем определить возникновение события transitionend для свойства display , то нужно использовать другой путь, когда прямоугольник невидим:
- Сначала мы удаляем класс hidden , устанавливаем свойство display: block , пока элемент еще визуально скрыт;
- После того, как это произошло, скрипт запускает таймер с помощью функции setTimeout() , после чего начинается анимация.
Обратите внимание, что некоторые браузеры требуют префикс для transitionend. Modernizr.prefixed() может в этом помочь.
Задержка равна 20 миллисекундам. Так как свойство display: block устанавливается мгновенно, то достаточно, чтобы элемент занял свою позицию на странице и можно запускать анимацию.
А как это сделали вы?
Как было замечено выше, возможно имеются и другие способы решения данной задачи, либо улучшения для приведенного мною решения.
Лично мне кажется, что использование функции setTimeout это оптимальный вариант. Тем более, что способ работает и, скорее всего не вызовет проблем, только если на странице не будут одновременно запускаться сотни анимаций. Но тогда это уже будет другая задача.
Вадим Дворников автор-переводчик статьи « Animating from “display: block” to “display: none »