От автора: Данная обучающая статья о том, как воссоздать эффект предзагрузки страницы, увиденный на сайте Fontface Ninja. Мы собираемся использовать CSS анимацию, 3D трансформации и SVG.
Сегодня мы хотим показать вам, как создать очень простой эффект предзагрузки страницы с помощью CSS анимации, SVG и JavaScript. Для сайтов, на которых очень важно, чтобы сначала загрузились все или только часть файлов, такого рода предзагрузочные заставки могут стать креативным способом скрасить утомительное ожидание посетителя сайта. Идея для данной обучающей статьи возникла благодаря красивому эффекту предзагрузки страницы, увиденному на сайте Fontface Ninja. Изначально, на первом плане появляются логотип и элемент загрузки в виде окружности. Когда анимирование загрузчика завершается, т.е. файлы страницы загружены, вся «шапка» поднимается наверх, а элементы страницы тем временем появляются, также с использованием анимации. И последний штрих – это логотип, скользящий наверх и меняющий при этом свой цвет.
В этой обучающей статье мы воссоздадим эффект, увиденный на сайте Fontface Ninja, внеся небольшие поправки, а во втором демо-примере будут использоваться чуточку другие эффекты. Для создания логотипа и элемента загрузки в виде окружности мы будем использовать встроенный SVG, чтобы иметь возможность стилизовать его с помощью CSS. Мы создадим небольшой скрипт для анимирования нашего SVG элемента загрузки. Анимационными эффектами на странице мы будем управлять с помощью классов, которые добавим к основному контейнеру.
Пожалуйста, обратите внимание на то, что мы будем использовать CSS анимацию и CSS 3D трансформации, так что они будут работать только в тех браузерах, которые их поддерживают.
Итак, давайте начнем!
Разметка
Давайте обернем элемент header и основную часть контента в контейнер. Мы должны помнить о том, что нам нужно контролировать все, что происходит с исходным видом страницы и контентом с классами. Поэтому мы будем использовать основной контейнер в качестве контрольного элемента. Мы зададим ему класс и идентификатор (ID) ip-container.
Исходный вид страницы состоит из элемента header, который включает в себя логотип и элемент загрузки. Оба они являются SVG элементами. Причем логотип является более сложным элементом, чем загрузчик, поэтому мы потом приведем координаты его пути, т.к. он действительно очень длинный. Как видите, мы устанавливаем значения для некоторых атрибутов SVG элемента, например, для ширины и высоты, для viewBox и preserveAspectRatio. Для атрибута preserveAspectRatio значение выглядит как xMidYMin meet, что означает, что мы принудительно используем универсальное масштабирование, чтобы графика полностью помещалась в родительский контейнер, была отцентрирована по оси X и поднята наверх. Для того чтобы логотип был доступным, мы добавляем заголовок, описание и необходимый ARIA атрибут – aria-labelledby.
Основному контенту задан класс ip-main. Позже мы будем применять анимацию к его дочерним элементам, заголовку, блоку с контентом и вложенным боксам:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<div id="ip-container" class="ip-container"> <!-- исходная шапка --> <header class="ip-header"> <h1 class="ip-logo"> <svg class="ip-inner" width="100%" height="100%" viewBox="0 0 300 160" preserveAspectRatio="xMidYMin meet" aria-labelledby="logo_title"> <title id="logo_title">Восхитительные демонстрации от Codrops</title> <path d="...наш супер-длинный путь..." /> </svg> </h1> <div class="ip-loader"> <svg class="ip-inner" width="60px" height="60px" viewBox="0 0 80 80"> <path class="ip-loader-circlebg" d="M40,10C57.351,10,71,23.649,71,40.5S57.351,71,40.5,71 S10,57.351,10,40.5S23.649,10,40.5,10z"/> <path id="ip-loader-circle" class="ip-loader-circle" d="M40,10C57.351,10,71,23.649,71,40.5S57.351,71,40.5,71 S10,57.351,10,40.5S23.649,10,40.5,10z"/> </svg> </div> </header> <!-- основной контент --> <div class="ip-main"> <h2>Чувствуйте себя как дома.</h2> <div class="browser clearfix"> <div class="box"> <span class="icon-bell"></span> <p>...</p> </div> <div class="box"> <span class="icon-heart"></span> <p>...</p> </div> <div class="box"> <span class="icon-cog"></span> <p>...</p> </div> </div> </div> </div><!-- /контейнер --> |
Давайте теперь применять стили.
CSS
Обратите внимание на то, что в CSS не содержится никаких вендорных префиксов, но вы найдете их в исходниках.
Первым делом мы подключим некоторые шрифты, которые мы будем использовать для «рыбного» текста и для иконок в боксах. Иконки, использующиеся в демо-примерах, взяты из набора Feather icon, и мы создали иконочный шрифт с помощью сервиса Icomoon App. Для «рыбного» текста используется шрифт Blokk, который классно подходит для создания эскизов и макетов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@font-face { font-weight: normal; font-style: normal; font-family: 'Blokk'; src: url('../fonts/blokk/BLOKKRegular.eot'); src: url('../fonts/blokk/BLOKKRegular.eot?#iefix') format('embedded-opentype'), url('../fonts/blokk/BLOKKRegular.woff') format('woff'), url('../fonts/blokk/BLOKKRegular.svg#BLOKKRegular') format('svg'); } @font-face { font-weight: normal; font-style: normal; font-family: 'feather'; src:url('../fonts/feather/feather.eot?-9jv4cc'); src:url('../fonts/feather/feather.eot?#iefix-9jv4cc') format('embedded-opentype'), url('../fonts/feather/feather.woff?-9jv4cc') format('woff'), url('../fonts/feather/feather.ttf?-9jv4cc') format('truetype'), url('../fonts/feather/feather.svg?-9jv4cc#feather') format('svg'); } |
Мы хотим, чтобы элемент header с самого начала полностью заполнял все окно просмотра, поэтому давайте зададим ему 100% для ширины и высоты, а также установим для свойства position значение fixed:
1 2 3 4 5 6 7 8 9 |
.ip-header { position: fixed; top: 0; z-index: 100; min-height: 480px; width: 100%; height: 100%; background: #f1f1f1; } |
Давайте обнулим отступы у заголовка логотипа:
1 2 3 |
.ip-header h1 { margin: 0; } |
И для логотипа и для загрузчика мы установим абсолютное позиционирование и растянем их на всю ширину окна просмотра:
1 2 3 4 5 6 7 8 9 |
.ip-logo, .ip-loader { position: absolute; left: 0; width: 100%; opacity: 0; cursor: default; pointer-events: none; } |
Вместо того, чтобы просто взять элемент с логотипом и спозиционировать его посередине элемента header, нам нужно помнить о следующем: нам нужно, чтобы SVG логотип был сам по себе отзывчивым, т.е. мы можем и не знать о его размерах, но мы хотим при этом поместить его сверху основного контента с помощью 3D трансформаций, после того как пройдет загрузка. В основном, из-за того, что мы не знаем размера нашего логотипа, мы не знаем, насколько нам придется его переместить, чтобы он оказался в верхней части контента (при процентных перемещениях отталкиваются от самого элемента, а не от его родителя). Но мы точно знаем и можем работать с одним конкретным значением: высота окна просмотра. Поэтому давайте просто зададим для логотипа значение 100% по высоте и переместим его на 25%, чтобы SVG логотип оставался посередине страницы:
1 2 3 4 5 |
.ip-logo { top: 0; height: 100%; transform: translate3d(0,25%,0); } |
Мы позиционируем загрузчик внизу окна просмотра:
1 2 3 |
.ip-loader { bottom: 20%; } |
SVG элементы, которым мы назначили класс ip-inner, будут отображаться как блочные элементы, и мы отцентрируем их по горизонтали с помощью значения auto для свойства margin:
1 2 3 4 |
.ip-header .ip-inner { display: block; margin: 0 auto; } |
SVG логотип должен быть отзывчивым, но не становится слишком большим или слишком маленьким. Поэтому, помимо процентного значения для ширины, мы также установим для него свойства максимальной и минимальной ширины:
1 2 3 4 5 |
.ip-header .ip-logo svg { min-width: 320px; max-width: 480px; width: 25%; } |
Поскольку мы встроили наш SVG логотип, мы можем прямо здесь же задать заливку пути:
1 2 3 |
.ip-header .ip-logo svg path { fill: #ef6e7e; } |
И то же самое для загрузчика:
1 2 3 4 |
.ip-header .ip-loader svg path { fill: none; stroke-width: 6; } |
Первый путь имеет серую заливку:
1 2 3 |
.ip-header .ip-loader svg path.ip-loader-circlebg { stroke: #ddd; } |
И второй путь будем иметь постепенный переход, который мы будем контролировать с помощью нашего JS. Но здесь мы установим значение для перехода stroke-dashoffset:
1 2 3 4 |
.ip-header .ip-loader svg path.ip-loader-circle { transition: stroke-dashoffset 0.2s; stroke: #ef6e7e; } |
А теперь мы добавим стили к контенту страницы, который обернут в блок div с классом ip-main:
1 2 3 4 5 6 7 |
.ip-main { overflow: hidden; margin: 0 auto; padding: 160px 0 10em 0; max-width: 1100px; width: 90%; } |
Размер заголовка будет задан в единицах vw, чтобы сделать его отзывчивым:
1 2 3 4 5 6 7 8 9 |
.ip-main h2 { margin: 0; padding: 0.5em 0 1em; color: #be4856; text-align: center; font-size: 4.25em; font-size: 4vw; line-height: 1; } |
Давайте добавим изображение с браузером:
1 2 3 4 5 6 7 8 9 10 11 |
.browser { margin: 0 auto; padding-top: 8%; min-height: 400px; max-width: 1000px; width: 100%; border-radius: 8px; background: #fff url(../img/browser.png) no-repeat 50% 0; background-size: 100%; color: #d3d3d3; } |
И некоторые «рыбные» боксы:
1 2 3 4 5 6 7 8 9 10 11 |
.box { float: left; padding: 3.5em; width: 33.3%; font-size: 0.7em; line-height: 1.5; } .box p { font-family: 'Blokk', Arial, sans-serif; } |
У каждого бокса будет иконка:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[class^="icon-"]::before, [class*=" icon-"]::before { display: block; margin-bottom: 0.5em; padding: 0.5em; border-radius: 5px; background: #dfdfdf; color: #fff; text-align: center; text-transform: none; font-weight: normal; font-style: normal; font-variant: normal; font-size: 5em; font-family: 'feather'; line-height: 1; speak: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-bell:before { content: "\e006"; } .icon-cog:before { content: "\e023"; } .icon-heart:before { content: "\e024"; } |
Теперь мы должны прописать всю анимацию, которая будет происходить. Как уже упоминалось ранее, мы будем контролировать запуск анимации с помощью добавления классов к основному контейнеру. Исходная анимация элемента header будет состоять в том, что все дочерние элементы будут двигаться снизу вверх:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
.loading .ip-logo, .loading .ip-loader { opacity: 1; animation: animInitialHeader 1s cubic-bezier(0.7,0,0.3,1) both; } .loading .ip-loader { animation-delay: 0.2s; } @keyframes animInitialHeader { from { opacity: 0; transform: translate3d(0,800px,0); } } |
Нам нужно только определить ключевой кадр from, поскольку мы хотим переместить элементы в их исходное положение. Задав значения для временной функции cubic-bezier, мы добавим приятную плавность к данному эффекту. Для элемента загрузки должна быть выставлена небольшая задержка перед появлением.
На данном этапе нужно запомнить, что мы будем анимировать процесс загрузки окружности с помощью JS. Поэтому нам понадобится еще одно «состояние», на которое мы сможем переключиться после завершения загрузки. Мы дадим контейнеру класс loaded и применим следующую анимацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
.loaded .ip-logo, .loaded .ip-loader { opacity: 1; } .loaded .ip-logo { transform-origin: 50% 0; animation: animLoadedLogo 1s cubic-bezier(0.7,0,0.3,1) forwards; } @keyframes animLoadedLogo { to { transform: translate3d(0,100%,0) translate3d(0,50px,0) scale3d(0.65,0.65,1); } } .loaded .ip-logo svg path { transition: all 0.5s ease 0.3s; fill: #fff; } .loaded .ip-loader { animation: animLoadedLoader 0.5s cubic-bezier(0.7,0,0.3,1) forwards; } @keyframes animLoadedLoader { to { opacity: 0; transform: translate3d(0,-100%,0) scale3d(0.3,0.3,1); } } |
Логотип перемещается вниз на 100% (помните, наш логотип занимает 100% высоты окна просмотра, поэтому это заставит его перемещаться вдоль всей высоты экрана), плюс еще немного для создания отступа, а затем мы его чуть-чуть уменьшим. Вместе с перемещением изменится и цвет SVG заливки. Элемент загрузки перемещается вверх, уменьшается и исчезает. Элемент header, находящийся в фиксированном положении, тоже должен быть поднят наверх:
1 2 3 4 5 6 7 |
.loaded .ip-header { animation: animLoadedHeader 1s cubic-bezier(0.7,0,0.3,1) forwards; } @keyframes animLoadedHeader { to { transform: translate3d(0,-100%,0); } } |
Давайте теперь позаботимся об элементах контента. Здесь вы можете применить множество креативных эффектов. Конечно, это полностью зависит от того, какой контент размещен на странице. В нашем случае нам нужно, чтобы элементы появлялись, двигаясь снизу вверх:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* Анимация для контента */ .loaded .ip-main h2, .loaded .ip-main .browser, .loaded .ip-main .browser .box, .loaded .codrops-demos { animation: animLoadedContent 1s cubic-bezier(0.7,0,0.3,1) both; } .loaded .ip-main .browser, .loaded .ip-main .browser .box:first-child { animation-delay: 0.1s; } .loaded .ip-main .browser .box:nth-child(2) { animation-delay: 0.15s; } .loaded .ip-main .browser .box:nth-child(3) { animation-delay: 0.2s; } @keyframes animLoadedContent { from { opacity: 0; transform: translate3d(0,200px,0); } } |
Небольшая задержка для боксов в внутри области с браузером позволит создать дополнительный изящный эффект. Чтобы избежать проблем со скроллингом и пустыми промежутками внизу страницы, нам нужно изменить позиционирование элемента header с фиксированного на абсолютное. Это мы можем проконтролировать путем добавления класса к элементу body (или любому другому родителю), после завершения всей анимации. С помощью этого класса мы переключаем позиционирование:
1 2 3 |
.layout-switch .ip-header { position: absolute; } |
Если использование JavaScript недоступно, мы показываем состояние после завершения анимации. Это можно сделать, задав для элемента header относительное позиционирование и соответственно изменив размеры логотипа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.no-js .ip-header { position: relative; min-height: 0px; } .no-js .ip-header .ip-logo { margin-top: 20px; height: 180px; opacity: 1; transform: none; } .no-js .ip-header .ip-logo svg path { fill: #fff; } |
И последний, но не менее важный, момент – мы должны позаботиться о большом заголовке и боксах на маленьких экранах:
1 2 3 4 5 6 7 8 9 10 11 12 |
@media screen and (max-width: 45em) { .ip-main h2 { font-size: 2.25em; font-size: 10vw; } .box { width: 100%%; } } |
Вот и все стилевое оформление.
JavaScript
JavaScript код состоит из двух частей. Мы отделим общий функционал по обработке загрузочного элемента от всего остального. Давайте назовем этот скрипт pathLoader.js, поскольку он анимирует именно путь элемента.
Нам бы хотелось иметь возможность установить параметр stroke-dashoffset, чтобы анимировать заливку пути. Сначала этот параметр и stroke-dasharray устанавливаются для длины пути (getTotalLength()). Мы рисуем путь, устанавливая для dash offset нижнее значение вплоть до нуля, когда путь полностью нарисован. Это реализуется благодаря вызову функции setProgress с параметром для значения. Также может оказаться полезным альтернативный параметр, если мы хотим исполнить некоторый код, когда значение уже установлено и переход завершен.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function PathLoader( el ) { this.el = el; // clear stroke this.el.style.strokeDasharray = this.el.style.strokeDashoffset = this.el.getTotalLength(); } PathLoader.prototype._draw = function( val ) { this.el.style.strokeDashoffset = this.el.getTotalLength() * ( 1 - val ); } PathLoader.prototype.setProgress = function( val, callback ) { this._draw(val); if( callback && typeof callback === 'function' ) { // give it a time (ideally the same like the transition time) so that the last progress increment animation is still visible. setTimeout( callback, 200 ); } } PathLoader.prototype.setProgressFn = function( fn ) { if( typeof fn === 'function' ) { fn( this ); } } |
Метод setProgressFn используется здесь, чтобы найти возможный путь взаимодействия с загрузчиком. К примеру, в нашем демо-примере не происходит предзагрузки, но мы имитируем загрузочную анимацию, устанавливая случайное значение от 0 до 1 через набор временных промежутков:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var simulationFn = function(instance) { var progress = 0, interval = setInterval( function() { progress = Math.min( progress + Math.random() * 0.1, 1 ); instance.setProgress( progress ); // reached the end if( progress === 1 ) { clearInterval( interval ); } }, 100 ); }; var loader = new PathLoader([pathselector]); loader.setProgressFn(simulationFn); |
Далее, давайте поместим оставшийся JS код в main.js. Сначала мы инициализируем и кэшируем некоторые переменные:
1 2 3 4 5 6 7 |
var support = { animations : Modernizr.cssanimations }, container = document.getElementById( 'ip-container' ), header = container.querySelector( 'header.ip-header' ), loader = new PathLoader( document.getElementById( 'ip-loader-circle' ) ), animEndEventNames = { 'WebkitAnimation' : 'webkitAnimationEnd', 'OAnimation' : 'oAnimationEnd', 'msAnimation' : 'MSAnimationEnd', 'animation' : 'animationend' }, // animation end event name animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ]; |
Мы начинаем исходную анимацию (появление логотипа и загрузчика) с добавления загрузочного класса к основному контейнеру. После завершения анимации для загрузочного SVG элемента мы используем «фальшивую» загрузочную анимацию, как объяснялось ранее. Обратите внимание на то, что пока выполняется анимация, мы не позволяем использовать скроллинг на странице.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function init() { var onEndInitialAnimation = function() { if( support.animations ) { this.removeEventListener( animEndEventName, onEndInitialAnimation ); } startLoading(); }; // отключаем скроллинг window.addEventListener( 'scroll', noscroll ); // исходная анимация classie.add( container, 'loading' ); if( support.animations ) { container.addEventListener( animEndEventName, onEndInitialAnimation ); } else { onEndInitialAnimation(); } } // скроллинга нет function noscroll() { window.scrollTo( 0, 0 ); } |
И снова мы будем имитировать процесс загрузки чего-либо, передавая нужную функцию для setProgressFn. Как только анимация завершится, мы заменяем класс loading на класс loaded, который запустит основную анимацию для элемента header и контента. После того, как это будет выполнено, мы добавляем к элементу body класс layout-switch и разрешаем скроллинг:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function startLoading() { // имитируем загрузку чего-либо... var simulationFn = function(instance) { var progress = 0, interval = setInterval( function() { progress = Math.min( progress + Math.random() * 0.1, 1 ); instance.setProgress( progress ); // достигнуто завершение if( progress === 1 ) { classie.remove( container, 'loading' ); classie.add( container, 'loaded' ); clearInterval( interval ); var onEndHeaderAnimation = function(ev) { if( support.animations ) { if( ev.target !== header ) return; this.removeEventListener( animEndEventName, onEndHeaderAnimation ); } classie.add( document.body, 'layout-switch' ); window.removeEventListener( 'scroll', noscroll ); }; if( support.animations ) { header.addEventListener( animEndEventName, onEndHeaderAnimation ); } else { onEndHeaderAnimation(); } } }, 80 ); }; loader.setProgressFn( simulationFn ); } |
Вот и все, все сделано! Мы надеемся, что вам понравилась данная обучающая статья, и она была для вас полезной и вдохновляющей!
Автор: Mary Lou
Источник: //tympanus.net/
Редакция: Команда webformyself.