Эффект для предзагрузки страницы.
Сегодня мы хотим показать вам, как создать очень простую страницу с эффектом предварительной загрузки используя 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