Анимация элементов с display:none и callback функции
Учимся делать корректную анимацию появления элемента на странице
Когда появляется необходимость сделать анимацию для появляющегося элемента, обычно мы не задумываемся и прибегаем к помощи jQuery.
Например:
$('.foo').fadeIn(300);
или
$('.foo').fadeOut(300);
Почему мы так поступаем? Потому что это не требует никаких усилий. jQuery позаботился даже о том, чтобы у нас была возможность вызвать callback-функцию после анимации:
$('.foo').fadeIn(300, doSomthingAfterIt);
Это очень быстро и эффективно. Но, к сожалению, появляется проблема: вся анимация, которую предлагает jQuery, в конечном счете является чистым JavaScript кодом, без малейшей примеси CSS.
Хороший frontend-разработчик должен знать, что переходы и анимации должны находиться в зоне ответственности CSS. Только так можно добиться лучшей производительности. Функции jQuery: .hide()
, .show()
, .toggle()
, .fadeIn()
, .slideUp()
не рекомендуются к использованию.
CSS переходы (transition)
Использование универсального CSS-свойства transition является верным подходом. Все что нужно сделать - это добавить класс с нужным свойством элементу:
.foo {
opacity: 0;
transition: opacity 300ms;
}
.fade-in {
opacity: 1;
}
$('.foo').addClass('fade-in');
Тем не менее использование свойства transition
связано с двумя проблемами:
Проблема 1
Как использовать callback-функции после transition?
Тут есть маленькая хитрость, а именно: событие transitionend
(подробно спецификацию можно прочитать тут и тут:
$('.foo')
.addClass('fade-in')
.one(transitionend, doSomethingAfterIt);
Теперь callback-функции доступны после любой анимации.
Проблема 2
Как сделать анимацию для элемента с исходным свойством display:none
Анимация для скрытого объекта - насущная проблема. Например, если мы добавим класс с нужными свойствами к такому элементу и используем transition, ничего не произойдет.
Такой код не будет работать:
/* исходное состояние элемента */
.foo{
display: none;
opacity: 0;
transition: opacity 300ms;
}
/* желаемый результат */
.foo.fade-in{
display: block;
opacity: 1;
}
Это происходит потому, что элемент не отрисован и не занимает места на экране, а это является обязательным условием для его анимации. То есть transition для элемента с display:none
не работает.
Метод Reflow
Для начала, нужно разобраться с терминами repaint и reflow. Если коротко:
repaint вызывается при изменениях, связанных с внешним видом элементов. Эти изменения не затрагивают размеры объекта и его положение на экране. Например, это могут быть CSS-свойства
opacity
,outline
,background-color
.reflow вызывается при изменении размеров элемента. В свою очередь это приводит к перерасчету макета страницы и дальнейшему обновлению экрана уже с учетом изменений.
По этой причине, для того, чтобы добавить анимацию элементу с исходным свойством display:none
, нужно форсировать reflow. Как это сделать? Добавим элементу класс со свойством display:block
и вызовем метод, возвращающий размеры элемента. Для определения размеров браузер будет вынужден обновить элемент и форсировать запуск reflow. После запуска reflow элемент уже занимает место на экране и выполнение анимации с помощью transition
становится возможным.
Вот пример рабочего кода:
var $foo = $('.foo');
$foo
.addClass('block')
.outerWidth(); // Reflow
$foo
.addClass('fade-in')
.one(transitionEnd, function() {
alert('Animated');
});
.block {
display: block;
}
.fade-in {
opacity: 1;
}
Спасибо за внимание.