Сегодня мы хотим показать вам, как создать простой и современный эффект перехода страницы. Может быть, вы заметили некоторые из интересных эффектов, замеченных на таких сайтах, как Nation или Keep Portland Weird, где два или более слоев наложений анимированы на странице, чтобы показать какой-то новый контент. Давайте посмотрим, как мы можем сделать что-то подобное. Мы сосредоточимся только на анимации, а не на загрузке Контента. Для эффектов мы будем использовать CSS анимации.
Разметка HTML
Для нашей простой демонстрации мы создадим макет полной ширины и высоты с некоторыми стрелками, которые вызовут наложение анимации. Идея состоит в том, чтобы показать страницу и в середине анимации наложения переключиться на другую страницу. Хотя мы будем просто работать со статическим контентом, вы можете подключить здесь свое динамическое решение, которое извлекает некоторые данные на лету.
Наша разметка выглядит следующим образом:
<body>
<main class="container">
<div class="pages">
<div class="page page--current">
<!-- intro page content -->
</div><!-- /page -->
<div class="page">
<!-- some other page -->
</div>
</div><!-- /pages -->
<nav class="pagenav">
<!-- buttons that will trigger the overlay animations -->
</nav>
</main>
</body>
Структура для наложения будет вставлена с помощью JavaScript. Мы будем работать с подразделением, которое размещено как фиксированный элемент поверх всего остального. В зависимости от направления, мы дадим этому элементу некоторые классы, которые помогут нам правильно применять стили. Наложение будет содержать несколько слоев, которые мы сможем определить. Каждый из слоев будет иметь Цвет фона, который мы установим, а также определенную анимацию в нашей таблице стилей.
Один пример того, как может выглядеть revealer:
<div class="revealer revealer--right">
<div class="revealer__layer"></div>
<div class="revealer__layer"></div>
<div class="revealer__layer"></div>
</div>
Каждый слой дополнительно будет иметь свой цвет фона. Чтобы создать уникальные эффекты, мы хотим анимировать каждый слой по отдельности. Для этого мы сначала устанавливаем родительский элемент в положение вне экрана, а затем анимируем слои. Эта начальная настройка основного элемента выполняется в нашем скрипте (см. ниже). Поведение анимации слоев затем определяется в нашей таблице стилей в зависимости от класса эффекта, а также других стилей для страницы, которая будет отображаться.
Стили CSS
Во-первых, давайте рассмотрим некоторые общие стили для нашего тела, контейнера и страниц (префиксы поставщиков опущены):
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. Мы хотим разместить revealer поверх всего, с фиксированной позицией:
.revealer {
width: 100vw;
height: 100vh;
position: fixed;
z-index: 1000;
pointer-events: none;
}
В зависимости от того, в каком направлении мы используем, нам нужно установить некоторые начальные стили позиционирования. В нашем сценарии, как мы увидим немного, мы установим некоторые другие преобразования, чтобы гарантировать, что верхняя сторона нашего revealer всегда обращена к экрану. Это упростит эффекты, потому что анимация внутреннего слоя всегда будет одинаковой (перемещение вверх), поэтому нам не нужно определять новую анимацию для каждого направления, если родительский слой повернут и расположен правильно:
.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 добавляется в тело, чтобы мы знали, какой эффект мы используем в данный момент. Revealer-animate дается revealer для того, чтобы вызвать анимации.
Как мы видим, мы добавляем одинаковые свойства анимации ко всем слоям, за исключением имени анимации. С тремя различными анимациями мы определяем задержки каждого из них в ключевых кадрах. Таким образом, мы гарантируем, что все анимации заканчиваются в один и тот же момент, но допускают различные тайминги внешнего вида:
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% на верхней части видимого экрана. Затем мы двигаем его в противоположном направлении, чтобы открыть новое содержание. То же самое происходит и с двумя другими слоями, только здесь мы запускаем их на разных keyframes, уменьшая “паузу”. Последний слой вообще не имеет паузы, он просто переходит на другую сторону, начиная с 25%.
JavaScript
Мы сделали небольшой плагин, который создаст revealer в зависимости от некоторых опций. По умолчанию используются следующие параметры:
Revealer.prototype.options = {
// total number of revealing layers (min is 1)
nmbLayers : 1,
// bg color for the revealing layers
bgcolor : '#fff',
// effect classname
effect : 'anim--effect-1',
// callback
onStart : function(direction) { return false; },
// callback
onEnd : function(direction) { return false; }
};
Функция добавления revealer со слоями и соответствующим классом эффектов выглядит следующим образом:
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 += '
';
}
this.revealerWrapper.innerHTML = strHTML;
bodyEl.appendChild(this.revealerWrapper);
};
Наиболее важная функция устанавливает начальное позиционирование нашего элемента reveal и добавляет классы управления для запуска анимации и выбранного эффекта.
В зависимости от того, какое направление мы выбираем, нам нужно, чтобы элемент reveal получил правильное преобразование. Помните, что мы просто вращаем и позиционируем показанное, таким образом, чтобы верхняя сторона всегда обращалась к экрану и слои всегда двигались вверх. Для угловых случаев мы должны убедиться, что ширина и высота revealer установлена правильно диагонали страницы.
Revealer.prototype.reveal = function(direction, callbacktime, callback) {
// if animating return
if( this.isAnimating ) {
return false;
}
this.isAnimating = true;
// current direction
this.direction = direction;
// onStart callback
this.options.onStart(this.direction);
// set the initial position for the layers´ parent
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;
// add direction and animate classes to parent
classie.add(this.revealerWrapper, 'revealer--' + direction || 'revealer--right');
classie.add(this.revealerWrapper, 'revealer--animate');
// track the end of the animation for all layers
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;
// callback
self.options.onEnd(self.direction);
}
});
});
// reveal fn callback
if( typeof callback === 'function') {
if( this.callbacktimeout ) {
clearTimeout(this.callbacktimeout);
}
this.callbacktimeout = setTimeout(callback, callbacktime);
}
};
И это все, мы надеемся, что вам понравился этот небольшой эффект и вы получили вдохновение.
Посмотреть демо