Эффект для предзагрузки страницы.
Сегодня мы хотим показать вам, как создать очень простую страницу с эффектом предварительной загрузки используя CSS-анимации, SVG и JavaScript. Для веб-сайтов, где важно, чтобы загрузка всего или части контента отображались на экране только после полной её загрузки - страница предзагрузки может быть отличным решением в подобной ситуации. К тому же это может служить поводом для создания чего-то оригинального и интересного, чтобы хоть как-то сделать ожидание загрузки контента менее скучным для посетителя.

Вдохновением для этого урока послужил сайт с подобным эффектом предзагрузки - Fontface Ninja. Первоначально логотип и круглый элемент прогресса загрузки имеют анимацию скольжения вверх, а когда загрузка завершается логотип и элемент загрузки эффектно подымаются, в то время как элементы страницы тоже имеют свою анимацию появления.
В этом уроке мы повторим эффект увиденный на Fontface Ninja с некоторыми коррективами, а также создадим второй вариант демо, со слегка другими эффектами. Для логотипа и круглого элемента прогресса загрузки мы будем использовать SVG графику, которую вставим инлайном в htmlразметку, так что мы их сможем стилизовать через CSS. Создадим также небольшой скрипт для хода анимации в SVG элементе загрузки.
Использовать мы будем CSS-анимации и CSS 3D-преобразования, так что работать всё это будет в актуальной/современной версии браузера.
Итак, давайте начнём!
Разметка
Давайте обернём header и основное содержимое страницы в контейнер. Делаем это мы для того, чтобы контролировать всё, что происходит в первоначальном виде страницы. Таким образом, мы будем использовать основной контейнер, как элемент управления. Присвоим ему класса и id ip-container
Первоначальный вид состоит из header’а, содержащего логотип и элемент процесса загрузки. Они оба в SVG, но наш логотип будет немного сложнее, чем кружок грузки, поэтому его код в теге path, что в примере ниже мы уберём, так как он очень длинный, но в исходниках он будет. Как вы можете видеть, мы задали некоторые атрибуты для SVG, такие как: ширина (width), высота (height), Viewbox и preserveAspectRatio. Значением для preserveAspectRatio является xMidYMin meet, которое определяет равномерное масштабирование в пределах своего контейнера, а его центр по оси X выравнивается по верхнему краю. Для того чтобы логотип был доступен, мы добавляем название к атрибуту aria-labelledby.
Основное содержимое страницы имеет класс ip-main, и в дальнейшем мы будем применять анимацию к его дочерним элементам:
<div id="ip-container" class="ip-container"> <!-- initial header --> <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">Delightful Demonstrations by Codrops</title> <path d="...our super-long path..." /> </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> <!-- main content --> <div class="ip-main"> <h2>Make yourself at home.</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><!-- /container -->
Идём дальше и переходим к стилям.
CSS
Обратите внимание, что CSSв данном уроке не будет содержать каких-либо префиксов, но вы найдёте их в исходниках.
Для начала мы подключим некоторые шрифты, которые понадобятся нам для текста и иконок. Иконки используем из демо-набора Feather icon set и шрифтовых иконок Icomoon App. Макет, имитирующий текст Blokk – это действительно отличная вещь при создании каркасов и макетов.
@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% по ширине и высоте, и установим фиксированное позиционирование:
.ip-header {
position: fixed;
top: 0;
z-index: 100;
min-height: 480px;
width: 100%;
height: 100%;
background: #f1f1f1;
}
Обнулим отступы для тега h1:
.ip-header h1 {
margin: 0;
}
И логотип, и прогресс загрузки будут иметь абсолютное позиционирование:
.ip-logo,
.ip-loader {
position: absolute;
left: 0;
width: 100%;
opacity: 0;
cursor: default;
pointer-events: none;
}
Вместо того чтобы просто взять логотип и расположить его в центре header’а, мы должны будем иметь в виду следующее: нам нужно чтобы сам SVGлоготип был адаптивным, и нам также нужно переместить его в начало основного содержимого страницы используя 3D-преобразования, когда загрузка завершится. Всё потому что мы не знаем размеров нашего логотипа, мы не знаем, сколько у нас на самом деле, но мы может работать с одной конкретной величиной: высота окна. Так что давайте просто установить логотип на 100% по высоте, и установим translate3d на 25%, так чтобы логотип расположился ровно посередине страницы:
.ip-logo {
top: 0;
height: 100%;
transform: translate3d(0,25%,0);
}
Позиционируем элемент загрузки в нижнюю часть экрана:
.ip-loader {
bottom: 20%;
}
SVG, которым мы присвоили класс ip-inner будут отображаться как блочный элемент. Отцентрируем их по горизонтали свойством margin, а именно значение autoих отцентрирует:
.ip-header .ip-inner {
display: block;
margin: 0 auto;
}
Также нужно учитывать то, что наш SVGлоготип должен быть не слишком большим и не слишком маленьким, поэтому зададим максимальную и минимальную ширину:
.ip-header .ip-logo svg {
min-width: 320px;
max-width: 480px;
width: 25%;
}
Так как мы вставили SVG логотип инлайном, то мы можем непосредственно менять его цвет в css:
.ip-header .ip-logo svg path {
fill: #ef6e7e;
}
И то же самое нам доступно и для прогресса загрузки:
.ip-header .ip-loader svg path {
fill: none;
stroke-width: 6;
}
Первый <path> прогресса загрузки имеет серую заливку:
.ip-header .ip-loader svg path.ip-loader-circlebg {
stroke: #ddd;
}
Второй <path> будет иметь прогресс перехода, который мы будем контролировать в JS. Но здесь, мы будем определять переход stroke-dashoffset:
.ip-header .ip-loader svg path.ip-loader-circle {
transition: stroke-dashoffset 0.2s;
stroke: #ef6e7e;
}

Cтили основного содержимого страницы завёрнуты в блок ip-main:
.ip-main {
overflow: hidden;
margin: 0 auto;
padding: 160px 0 10em 0;
max-width: 1100px;
width: 90%;
}
Размер шрифта заголовка будет иметь единицы VW, который зависят и определяют размеры элемента-(ов) в нашем случае размер шрифта от размера окна браузера, то есть VWявляется адаптивной единицей:
.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;
}
Давайте добавим картинку для нашего импровизированного браузера:
.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;
}
И три бокса внутри нашего браузера:
.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;
}
Каждый из боксов будет иметь свой значок:
[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’a будут движения снизу вверх:
.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 keyframes.
Функция cubic-bezier добавит хороший эффект плавности движения. Элемент загрузки должен иметь небольшую задержку (animation-delay), перед тем как он начнёт своё движение вверх.
Так же мы должны будем переключать "состояние", как только анимация прогресса закончится. Добавим класс loaded к контейнеру и применим следующую анимацию:
.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 должен также двигаться вверх:
.loaded .ip-header {
animation: animLoadedHeader 1s cubic-bezier(0.7,0,0.3,1) forwards;
}
@keyframes animLoadedHeader {
to { transform: translate3d(0,-100%,0); }
}
Давайте позаботимся об элементах контента. Здесь, конечно можно проявить творческий подход и сделать что-то своё. Все зависит от того какое содержимое у вас на странице. В нашем случае, мы хотим, чтобы элементы исчезли, в то время как они перемещаются сверху вниз:
/* Content animations */
.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);
}
}
Небольшая задержка для боксов в нашем импровизированном браузере, создаст изящный дополнительный эффект.
Для того чтобы избежать проблем с прокруткой и пробелов в нижней части страницы, мы должны перейти от фиксированного к абсолютному позиционированию:
.layout-switch .ip-header {
position: absolute;
}
Если JavaScript в браузере отключён, то нам желательно показать состояние после всех анимаций. Это можно сделать, установив header на относительное позиционирование и задать размер для логотипа:
.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;
}
И последнее, но не менее важное, мы должны позаботиться об адаптивности заголовка и боксов для мобильных экранов:
@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-dashoffset и stroke-dasharray установлены на длине пути (getTotalLength ()). Мы рисуем линию, задав смещение штриха на более низкое значение вплоть до нуля, где путь полностью нарисован. Это делается путём вызова функции setProgress с параметром value. Необязательный параметр callback может быть полезен, если мы хотим выполнить несколько сценариев, как только значение установлено и переход закончен.
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 на протяжении множества временных интервалов:
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);
Далее, давайте создадим наш скрипт покоя в main.js. Сначала мы инициализируем и кэшируем некоторые переменные:
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' ) ];
Начнём начальную анимацию (когда логотип, и элемент загрузки скользят вверх), добавив класс loading к главному контейнеру. После завершения анимации запускаем "фальшивую" загрузку - анимация заполнения процесса загрузки, как было объяснено выше. Обратите внимание, в то время как эти анимации воспроизводятся, мы не позволяем страницу скроллить.
function init() {
var onEndInitialAnimation = function() {
if( support.animations ) {
this.removeEventListener( animEndEventName, onEndInitialAnimation );
}
startLoading();
};
// disable scrolling
window.addEventListener( 'scroll', noscroll );
// initial animation
classie.add( container, 'loading' );
if( support.animations ) {
container.addEventListener( animEndEventName, onEndInitialAnimation );
}
else {
onEndInitialAnimation();
}
}
// no scroll
function noscroll() {
window.scrollTo( 0, 0 );
}
Опять же, мы будем симулировать, что что-то загружается, передавая пользовательскую функцию в setProgressFn. После того, как анимация завершена, заменяем класс loading на класс loaded, который будет инициировать основные анимации для header’aи контента. После того как всё будет готово, мы добавим класс layout-switch к тегу body и позволим скролить страницу:
function startLoading() {
// simulate loading something..
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 ) {
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 );
}
И это все, все готово!
Перевод: Art
Оригинал: tympanus.net