Создаем эффект предварительной загрузки страницы » Techno-Co — Всё для вебмастера, для создание сайтов с нуля. {title}

Сегодня мы хотим рассказать вам о том, как создать очень простой эффект предварительной загрузки страницы при помощи CSS-анимаций, SVG и javascript. Для сайтов, на который важно представить вниманию посетителя сразу весь важный контент, такой эффект может быть очень кстати. Суть данного руководства заключается в том, чтобы воссоздать эффект, который можно видеть на сайте Fontface Ninja. Для начала, вниманию пользователей будет представлен логотип веб-сайта и анимация, обозначающая подгрузку остальных элементов, и сразу после того, как все нужные элементы будут загружены, вся шапка уедет вверх, и отобразится весь контент. Скользящий логотип с изменяющимся цветом создаст необходимый эффект.Сегодня мы займемся воссозданием эффектом, который можно видеть на сайте Fontface Ninja, однако с некоторыми изменениями, а также разработаем второе демо с изменениями в эффекте. Для реализации логотип и анимации загрузки мы воспользуемся SVG-графикой, которую можно редактировать при помощи CSS-документа. Мы создадим небольшой скрипт для анимации контура SVG, предполагающего загрузку, а затем будем изменять анимацию на странице посредством классов, которые будут добавлены в основной контейнер.Пожалуйста учтите, что мы будем использовать CSS-анимации и CSS 3D transform, так что все это будет правильно работать только в тех браузерах, где имеется соответствующая поддержка.Итак, давайте приступим!

Разметка

Давайте обернем заголовочную часть и разделение с основным контентом в контейнер. Нам следует учитывать, что контроль за происходящим производится посредством классов. Так что, мы будем использовать основной контейнер в качестве элемента управления. Мы зададим ему класс и id ip-container.

Исходный вид состоит из заголовочной части, которая содержит логотип и элемент загрузки. Оба эти элемента выполнены при помощи SVG, а наш логотип будет немного сложнее, чем загрузчик, так что мы поместим все координаты путей в ниже приведенный фрагмент кода, так как он будет очень длинным. Как видно, мы определяем несколько SVG-атрибутов вроде width, height, viewBox и preserveAspectRatio. Значением для preserveAspectRatio выступает xMidYMin, что значит, что мы используем принудительное равномерное масштабирование, чтобы графика идеально вкладывалась в контейнер, центрированный по оси X и выравненный по верхнему краю. Для того чтобы можно было осуществить доступ к логотипу, мы добавляем заголовок, описание и необходимый атрибут ARIA, aria-labelledby.

К основному контенту применен класс ip-main и позже мы добавим анимацию к его дочерним элементам, заголовку, разделению и внутренним блокам:






        

Delightful Demonstrations by Codrops














        

Make yourself at home.





                
...
...
...

Давайте продолжим, и оформим все это.

CSS-код

Учтите, что CSS не содержит браузерных префиксов, однако в файлах с исходным кодом они присутствуют.

В самом начале мы включаем сюда несколько шрифтов, которые нужны для оформления текста-заглушки, а также иконки в блоки. Иконки, используемые в демо, были взяты из набора Feather, и мы создали иконический шрифт при помощи приложения 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');
}

Нам нужно, чтобы изначально заголовочная часть заполняла все окно просмотра, поэтому давайте зададим ей 100% ширины и высоты, и выставим позиционирование на fixed:

.ip-header {
    position: fixed;
    top: 0;
    z-index: 100;
    min-height: 480px;
    width: 100%;
    height: 100%;
    background: #f1f1f1;
}

Давайте уберем все поля из заголовка в логотипе:

.ip-header h1 {
    margin: 0;
}

Как логотип, так и загрузчик будут позиционированы абсолютно, и мы растянем их на все окно просмотра:

.ip-logo,
.ip-loader {
    position: absolute;
    left: 0;
    width: 100%;
    opacity: 0;
    cursor: default;
    pointer-events: none;
}

Вместо того, чтобы брать элемент logo и позиционировать его по центру заголовочной части, нам нужно учитывать следующее: нам нужно, чтобы сам SVG-файл логотипа была адаптивен, что означает, что у нас нет информации о его размере, и нам нужно переместить его в самых верх основного контента при помощи 3D transforms сразу после завершения загрузки. В основном потому, что мы не в курсе размеров логотипа, и не знаем, как он будет перемещаться в самый верх контента (процентные значения используют элемент лишь в качестве опорной точки, а не в качестве родительского элемента). Однако мы знаем и можем работать с одним определенным значением: высотой viewport. Итак, давайте просто выставим логотип на 100% высоты и переместим его на 25%, чтобы SVG-логотип был зафиксирован по центру страницы:

.ip-logo {
    top: 0;
    height: 100%;
    transform: translate3d(0,25%,0);
}

Мы помещаем элемент logo в самый низ viewport:

.ip-loader {
    bottom: 20%;
}

SVG-коды, которым мы задали класс ip-inner, будут отображены в виде блочного элемента, и мы центрируем их по горизонтали при помощи auto margin:

.ip-header .ip-inner {
    display: block;
    margin: 0 auto;
}

SVG-код логотипа должен быть адаптивным, однако не должен становиться слишком маленьким. Итак, кроме процентного значения в качестве ширины, мы также выставляем max и min width:

.ip-header .ip-logo svg {
    min-width: 320px;
    max-width: 480px;
    width: 25%;
}

Так как мы добавили SVG-код логотипа строчным образом, мы можем напрямую применить стили для цвета пути:

.ip-header .ip-logo svg path {
    fill: #ef6e7e;
}

И то же самое подходит для загрузчика:

.ip-header .ip-loader svg path {
    fill: none;
    stroke-width: 6;
}

Первый путь будет окрашен серым цветом:

.ip-header .ip-loader svg path.ip-loader-circlebg {
    stroke: #ddd;
}

А второй путь будет оформлен переходом прогресса, которым мы можем управлять посредством JS-кода. Однако здесь мы определяем переход stroke-dashoffset:

.ip-header .ip-loader svg path.ip-loader-circle {
    transition: stroke-dashoffset 0.2s;
    stroke: #ef6e7e;
}

А теперь, давайте применим стили к контенту на странице, который обернут в разделение ip-main:

.ip-main {
    overflow: hidden;
    margin: 0 auto;
    padding: 160px 0 10em 0;
    max-width: 1100px;
    width: 90%;
}

Заголовок будет масштабирован при помощи 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";
}

Теперь нам нужно определить анимации, которые будут происходить на странице. Как уже было отмечено ранее, мы будем контролировать запуск анимаций посредством добавления классов к основному контейнеру.Исходная анимация элементов заголовочной части поместит их на экран из нижней части:

.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 контейнеру и применим следующую анимацию:

.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-пути будет изменяться посредством transition.Элемент загрузчика двигается вверх, уменьшается и испаряется.Фиксированная заголовочная часть также должна слегка сместиться вверх:

.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);
    }
}

Небольшая задержка блоков в разделении браузера создаст дополнительный привлекательный эффект.Чтобы избежать проблем с прокруткой и пустых мест на дне страницы, нам нужно переключить абсолютное позиционирование заголовочной части с fixed на absolute. Это можно сделать за счет добавления класса к body (или к любому родительскому элементу), после того, как все анимации будут завершены. При помощи данного класса мы можем переключать позиционирование:

.layout-switch .ip-header {
    position: absolute;
}

Если у нас нет поддержки javascript, то нужно сразу показывать состояние после завершения всех анимаций. Мы можем сделать это, задав заголовочной части относительное позиционирование, и соответствующим образом масштабировав логотип:

.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 состоит из 2 частей. Мы отделим функционал основного элемента загрузки от всего остального. Давайте запустим скрипт pathLoader.js, так как это элемент пути, который и нужно анимировать. Нам нужно сохранить возможность устанавливать stroke-dashoffset для того, чтобы анимировать заполнение пути. Изначально, этот параметр и stroke-dasharray выставлены на длину пути (getTotalLength()). Мы рисуем путь за счет установки dashoffset на меньшее значение вплоть до нуля. Это реализуется за счет функции setProgress с параметром в качестве значения. Опциональный 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 к основному контейнеру. После завершения анимации мы запускаем «фальшивую» анимацию в SVG-элементе загрузчика, как это оговаривалось ранее. Учтите, что пока происходят эти анимации, мы не позволяем прокручивать страницу.

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, который запускает основную анимацию заголовочной части и контента. Как только это будет завершено, мы добавляем класс 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 );
}

И на этом все!

Исходники можно скачать архивом.