Анимация от «display: block» до «display: none»

Возможно вам известно, что с помощью свойств transition и animation в CSS3 можно создавать анимацию для определенных CSS-свойств. Но есть свойства, не поддающиеся анимации. Одним из них является display.

Было бы очень полезно анимировать это свойство, но это невозможно, как мне казалось до недавнего времени (ну например, как анимировать нечисловое свойство display: table?). Но есть обходной путь, который я покажу в данной статье.

Зачем может понадобиться анимировать свойство «display»?

Нужда в анимации свойства display может исходить от необходимости решить следующие проблемы:

  • Нужно сделать плавное исчезновение объекта со страницы;
  • Вы не хотите, чтобы элемент занимал место после своего исчезновения (например, нужно вызвать перестройку потока вывода после исчезновения некоторого объекта);
  • Вы хотите использовать для анимации исключительно CSS, без применения сторонних библиотек.

Исходя из вышеперечисленного, изменения значения свойства opacity до нуля недостаточно, так как элемент с нулевой прозрачностью все еще будет занимать место на странице.

Давайте пошагово решим поставленную задачу.

Использование Opacity и Display

Первым делом, мы попробуем использовать оба свойства — и opacity и display. HTML-код будет таким:

<div id="box" class="box">  
</div>  
<button>Переключатель видимости</button>

CSS-код может выглядеть так:

.box {  
background: goldenrod;  
width: 300px;  
height: 300px;  
margin: 30px auto;  
transition: all 2s linear;  
display: block;  
}  
.hidden {  
display: none;  
opacity: 0;  
}

В классе «hidden» у нас имеются значения display: none и opacity: 0. Если мы будем подставлять данный класс с помощью jQuery, то можно использовать такой код:

var box = $('#box');  
$('button').on('click', function () {  
 box.toggleClass('hidden');  
});

Однако если мы сделаем так, то не увидим эффекта перехода, который определен в стилевом блоке .box. Получится вот что (пример в Firefox немного отличается от Chrome/IE10):

Нажмите несколько раз на кнопку «Переключение видимости» и увидите, что прямоугольник исчезает и появляется мгновенно, без перехода.

Чтобы это исправить, мы можем попробовать отделить свойство display от opacity в CSS-файле:

.hidden {  
display: none;  
}  
.visuallyhidden {  
 opacity: 0;  
}

Теперь мы можем переключать оба класса по очереди:

var box = $('#box');  
$('button').on('click', function () {  
 box.toggleClass('visuallyhidden');  
 box.toggleClass('hidden');  
});

Однако данный вариант не поможет нам выполнить поставленной задачи:

Нам нужно, чтобы элемент сначала плавно исчезал со страницы визуально, а затем полностью удалялся.

Даже если мы скомбинируем opacity: 0 с visibility: hidden, то несмотря на присутствие анимации, элемент по-прежнему будет занимать место на странице после исчезновения, что опять же нас не устраивает.

Почему не работает?

Перед применением моего метода, я хочу пояснить, почему мы не можем решить задачу простой поочередной заменой классов.

Во-первых, если вы будете добавлять классы так, как показано в примере выше, даже при нормально работающем переходе, необходимо будет разделить секцию для удаления и замены классов (то есть, если прямоугольник начинает скрываться, то сначала нужно установить свойство display: block, а затем изменить прозрачность).

Но этот прием не работает. JavaScript выполняет строку за строкой и не ждет выполнения операций, которые запускает каждая из них. Другими словами, если вы запускаете анимацию или другое асинхронное событие в строке 1, строка 2 будет выполнена, даже если анимация еще не закончилась.

После запуска анимации свойством opacity, а ее выполнение требует определенного времени, вы не увидите процесса, потому что строка display: none выполняется тут же.

Давайте подытожим возникшие перед нами проблемы:

  • Когда элемент видим, сначала запускаем анимацию прозрачности, а затем, после её окончания, применяем строку display: none;
  • Когда элемент невидим, сначала устанавливаем свойство display: block, а затем пока он еще невидим, но уже присутствует на странице, запускаем анимацию.

Возможное решение

Возможно, есть и другие способы решения данной проблемы, но мой способ таков.

CSS-код не менется (два класса hidden по-прежнему разделены), а вот код jQuery будет таким:

var box = $('#box');  
$('button').on('click', function () {  
 if (box.hasClass('hidden')) {  
 box.removeClass('hidden');  
setTimeout(function () {  
box.removeClass('visuallyhidden');  
 }, 20);  
 } else {  
 box.addClass('visuallyhidden');  
box.one('transitionend', function(e) {  
box.addClass('hidden');  
});  
 }  
});

А вот пример в действии:

Вот что этот код делает, когда прямоугольник видим:

  • Добавляет класс 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” был подготовлен дружной командой проекта Сайтостроение от А до Я.