Сегодня мы хотели бы показать вам, как создать простой и современный эффект перехода с одной страницы на другую.
HTML-разметка
Для нашей простенькой демо-страницы мы создадим макет, занимающий всю ширину и высоту окна браузера, со стрелками, которые будут вызывать наложения анимаций. Идея заключается в том, чтобы показать страницу и, в середине наложения анима,ций перейти на другую страницу. В то время как мы будем работать со статическим контентом, вы можете реализовать динамическую подгрузку данных. Наша HTML-разметка выглядит следующим образом:
<body>
<main class="container">
<div class="pages">
<div class="page page--current">
<!-- текущая страница -->
</div><!-- /page -->
<div class="page">
<!-- другие страницы -->
</div>
</div><!-- /pages -->
<nav class="pagenav">
<!-- кнопки, вызывающие наложения анимаций -->
</nav>
</main>
</body>
Структура наложения вставляется с помощью JavaScript. Поработаем с разделением, которое размещено и закреплено над всем остальным. В зависимости от направления, мы назначим элементу спец-классы, которые помогут нам правильно применить стили. Наложение будет содержать ряд слоев, каждый из которых будет иметь цвет фона, а также конкретные анимации, заданные в таблице стилей.
Один из примеров как может выглядеть “раскрыватель”:
<div class="revealer revealer--right">
<div class="revealer__layer"></div>
<div class="revealer__layer"></div>
<div class="revealer__layer"></div>
</div>
Для создания уникальных эффектов нам необходимо оживить каждый слой по отдельности. Для этого мы устанавливаем их родителя изначально за пределами экрана, а затем анимируем слои. Эта начальная установка родительского элемента делается в нашем скрипте (смотри ниже). Поведение анимации слоев определяется в нашей таблице стилей в зависимости от класса эффекта, наряду с остальными стилями появляющейся страницы.
CSS стили
Во-первых, давайте взглянем на некоторые общие стили для body, контейнера .container
и страниц .page
(префиксы опущены):
html,
body {
min-height: 100vh;
overflow-x: hidden;
}
.js .container {
position: relative;
height: 100vh;
overflow: hidden;
}
.js .pages {
position: relative;
overflow: hidden;
width: 100vw;
height: 100vh;
z-index: 0;
}
.page {
padding: 6.5em;
background: #66c6ff;
display: flex;
flex-direction: column;
}
.js .page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
visibility: hidden;
z-index: 1;
}
.js .page--current {
visibility: visible;
position: relative;
}
Мы хотим, чтобы наша страница была во всю ширину и высоту окна браузера, при этом все, что вне - не показывалось. По умолчанию, а также в JS (мы используем Modernizr), страницы скрыты, а текущий класс настроен так, что соответствующая ему страница видна. Мы хотим убедиться, что наш макет работоспособен без JavaScript, поэтому мы добавим "условные" стили.
Посмотрим, как выглядит стиль раскрывателя. Попробуем поместить его в самый верх, и там же закрепить:
.revealer {
width: 100vw;
height: 100vh;
position: fixed;
z-index: 1000;
pointer-events: none;
}
В зависимости от используемого направления, нам нужно несколько начальных стилей позиционирования. В нашем скрипте, как мы скоро сможем заметить, мы сделаем несколько переходов для того, чтобы убедиться в том, что наш раскрыватель всегда направлен своей верхней стороной к экрану. Это упростит эффекты анимации, потому что внутренний слой всегда будет таким же (перемещение вверх), так что нам не нужно будет определять новые анимации для каждого направления, если родитель правильно вращается и расположен:
.revealer--cornertopleft,
.revealer--cornertopright,
.revealer--cornerbottomleft,
.revealer--cornerbottomright {
top: 50%;
left: 50%;
}
.revealer--top,
.revealer--bottom {
left: 0;
}
.revealer--right,
.revealer--left {
top: 50%;
left: 50%;
}
.revealer--top {
bottom: 100%;
}
.revealer--bottom {
top: 100%;
}
Слои будут иметь цвет фона по умолчанию, который затем будет индивидуально подобран для каждого эффекта:
.revealer__layer {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: #ddd;
}
Давайте посмотрим пример эффекта. Трехслойный эффект добавляет анимацию к каждому слою с помощью специальной функции смягчения. Класс .anim–effect
class добавляется к body, так что мы знаем, какой эффект сейчас используется. Класс .revealer–animate
присвоен раскрывателю, чтобы вызвать анимацию.
Как видите, мы добавляем те же свойства анимации для всех слоев, за исключением имени анимации, указываемой в CSS свойстве animation-name. С помощью трех разных анимаций мы сможем определить задержки каждой из них в пределах ключевых кадров. Таким образом мы убедимся в том, что все анимации заканчиваются в один и тот же момент, но имеют разное время действия:
.anim--effect-3 .page:nth-child(2) {
background: #F3A3D3;
}
.anim--effect-3 .revealer--animate .revealer__layer {
animation: anim-effect-3-1 1.5s cubic-bezier(0.550, 0.055, 0.675, 0.190) forwards;
}
.anim--effect-3 .revealer--animate .revealer__layer:nth-child(2) {
animation-name: anim-effect-3-2;
}
.anim--effect-3 .revealer--animate .revealer__layer:nth-child(3) {
animation-name: anim-effect-3-3;
}
@keyframes anim-effect-3-1 {
0% {
transform: translate3d(0, 0, 0);
}
25%, 75% {
transform: translate3d(0, -100%, 0);
animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
100% {
transform: translate3d(0, -200%, 0);
}
}
@keyframes anim-effect-3-2 {
0%, 12.5% {
transform: translate3d(0, 0, 0);
}
37.5%, 62.5% {
transform: translate3d(0, -100%, 0);
animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
87.5%, 100% {
transform: translate3d(0, -200%, 0);
}
}
@keyframes anim-effect-3-3 {
0%, 25% {
transform: translate3d(0, 0, 0);
animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
75%, 100% {
transform: translate3d(0, -200%, 0);
}
}
Вот, что мы делаем здесь: первый слоя движется, затем наступает "пауза" с середины до 75%, пока он находится в пределах видимости на экране. Затем мы перемещаем слой в противоположном направлении, чтобы показать новое содержание. То же самое происходит и для двух других слоев, но здесь мы начинаем с других ключевых кадров, уменьшая "паузу". Последний слой не имеет паузу вообще, он просто проходит насквозь, начиная с 25%.
JavaScript
Мы сделали небольшой плагин, который создает раскрыватели в зависимости от определенных настроек. Вот какие у него дефолтные параметры:
Revealer.prototype.options = {
// число раскрывающихся слоев (мин. 1)
nmbLayers : 1,
// фоновый цвет для раскрываемых слоев
bgcolor : '#fff',
// имя класса с эффектом
effect : 'anim--effect-1',
// ф-я обратного вызова перед эффектом
onStart : function(direction) { return false; },
// ф-я обратного вызова в конце эффекта
onEnd : function(direction) { return false; }
};
Функция для добавления раскрывателей, слоев и соответсвующих классов эффектов выглядит следующим образом:
Revealer.prototype._addLayers = function() {
this.revealerWrapper = document.createElement('div');
this.revealerWrapper.className = 'revealer';
classie.add(bodyEl, this.options.effect);
var strHTML = '';
for(var i = 0; i < this.options.nmbLayers; ++i) {
var bgcolor = typeof this.options.bgcolor === 'string' ? this.options.bgcolor : (this.options.bgcolor instanceof Array && this.options.bgcolor[i] ? this.options.bgcolor[i] : '#fff');
strHTML += '<div class="revealer__layer" style="background:' + bgcolor + '"></div>';
}
this.revealerWrapper.innerHTML = strHTML;
bodyEl.appendChild(this.revealerWrapper);
};
Функция устанавливает начальное позиционирование нашего раскрывающегося элемента и добавляет классы управления для запуска анимации и выбранного эффекта.
В зависимости от выбранного направления, нам нужно будет убедиться в том, что раскрывание происходит правильно. Имейте в виду, что мы просто вращаем и размещаем раскрыватель так, чтобы верхняя его сторона всегда была направлена к экрану, чтобы слои могли всегда двигаться вверх. В случаях с углами нам нужно также убедиться в том, что ширина и высота раскрывателя заданы верно по отношению к диагоналям страницы. В случае с левой или правой стороной, убеждаться нужно будет в том, что высота раскрывателя равна его ширине, поскольку вращать его мы будем на 90 градусов.
Revealer.prototype.reveal = function(direction, callbacktime, callback) {
// если анимация возвращается
if( this.isAnimating ) {
return false;
}
this.isAnimating = true;
// текущее направление
this.direction = direction;
// ф-я обратного вызова перед запуском эффекта
this.options.onStart(this.direction);
// Установить начальное положение для родителя слоев
var widthVal, heightVal, transform;
if( direction === 'cornertopleft' || direction === 'cornertopright' || direction === 'cornerbottomleft' || direction === 'cornerbottomright' ) {
var pageDiagonal = Math.sqrt(Math.pow(winsize.width, 2) + Math.pow(winsize.height, 2));
widthVal = heightVal = pageDiagonal + 'px';
if( direction === 'cornertopleft' ) {
transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,135deg) translate3d(0,' + pageDiagonal + 'px,0)';
}
else if( direction === 'cornertopright' ) {
transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,-135deg) translate3d(0,' + pageDiagonal + 'px,0)';
}
else if( direction === 'cornerbottomleft' ) {
transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,45deg) translate3d(0,' + pageDiagonal + 'px,0)';
}
else if( direction === 'cornerbottomright' ) {
transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,-45deg) translate3d(0,' + pageDiagonal + 'px,0)';
}
}
else if( direction === 'left' || direction === 'right' ) {
widthVal = '100vh'
heightVal = '100vw';
transform = 'translate3d(-50%,-50%,0) rotate3d(0,0,1,' + (direction === 'left' ? 90 : -90) + 'deg) translate3d(0,100%,0)';
}
else if( direction === 'top' || direction === 'bottom' ) {
widthVal = '100vw';
heightVal = '100vh';
transform = direction === 'top' ? 'rotate3d(0,0,1,180deg)' : 'none';
}
this.revealerWrapper.style.width = widthVal;
this.revealerWrapper.style.height = heightVal;
this.revealerWrapper.style.WebkitTransform = this.revealerWrapper.style.transform = transform;
this.revealerWrapper.style.opacity = 1;
// добавить направление и animate classes to parent
classie.add(this.revealerWrapper, 'revealer--' + direction || 'revealer--right');
classie.add(this.revealerWrapper, 'revealer--animate');
// отслеживать конец анимации для всех слоев
var self = this, layerscomplete = 0;
this.layers.forEach(function(layer) {
onEndAnimation(layer, function() {
++layerscomplete;
if( layerscomplete === self.options.nmbLayers ) {
classie.remove(self.revealerWrapper, 'revealer--' + direction || 'revealer--right');
classie.remove(self.revealerWrapper, 'revealer--animate');
self.revealerWrapper.style.opacity = 0;
self.isAnimating = false;
// функция обратного вызова
self.options.onEnd(self.direction);
}
});
});
// reveal fn callback
if( typeof callback === 'function') {
if( this.callbacktimeout ) {
clearTimeout(this.callbacktimeout);
}
this.callbacktimeout = setTimeout(callback, callbacktime);
}
};
Вот и все, мы надеемся, что вам понравился наш маленький эффект и вы его обязательно где-нибудь используете.