Выполняю, подождите...
 
 
Библиотека

Параллакс эффект c помощью CSS и JS

Параллакс эффект — довольно интересное дополнение к стилистике веб-страниц, которое позволяет оживить дизайн сайта. С реализацией новых возможностей CSS3 в браузерах достичь этого эффекта стало достаточно просто. В этой статье мы рассмотрим несколько примеров, как сделать параллакс эффект, используя возможности CSS и JavaScript.

Способы сделать параллакс эффект

Как правило, весь фокус эффекта заключается в перемещении нескольких слоев, фонов этих слоев или иных элементов страницы с разной скоростью, как относительно друг друга, так и относительно неподвижных частей страницы, при этом направление движения этих элементов должно совпадать. Такой нехитрой манипуляцией на двумерной плоскости, мы создаем иллюзию смещения в трехмерном пространстве относительно «отдаленных» объектов, когда наши зрительные центры находят похожие образы перемещений относительно тех, которые мы ранее наблюдали в реальном мире.

Для создания убедительного параллакс эффекта не нужно делать десятки слоев и анимировать их, но достаточно сделать всего 3-4 слоя, где каждый более «отдаленный» от зрителя слой будет двигаться медленней относительно предыдущего на определенное значение.

Если для создания параллакса используются фоновые изображения в блоках с разной вложенностью, то в этом случае удобно использовать свойство background-position для смещения фоновых изображений, но если в эффекте параллакса замешаны абсолютно-позиционируемые слои, то в этом случае придется оперировать свойствами их положения left, top и т.д. Но гораздо удобнее использовать возможности CSS3 и свойство transform, которое позволяет избежать некоторые ненужные расчеты, особенно при масштабировании страницы сайта. К тому же, transform позволяет сделать множество других крутых эффектов совместно с параллаксом, но это уже тема для отдельной статьи. Ну, а пока рассмотрим два примера создания эффекта: с фоновыми изображениями и с блоками.

Анимацию параллакса возможно приспособить под разные события на веб-странице сайта. Например, при прокрутке страницы, по нажатию кнопок клавиатуры, это может быть и просто зацикленная анимация, но вот еще один интересный способ эффекта параллакса — по движению мыши. По большей части способы реализации разных видов параллакс эффектов схожи, поэтому здесь мы не будем рассматривать много способ, а разберем детально зацикленную анимацию, а также параллакс по движению мыши, чего будет вполне достаточно для понимания.

Параллакс фона

Несмотря на то, что для создания параллакса фона мы будем пользоваться фоновыми изображениями, все же нам потребуется создать несколько контейнеров друг внутри друга с одинаковыми размерами, либо позиционированными так, чтобы они полностью перекрывали друг друга, но каждый со своим фоновым изображением.

Давайте рассмотрим всё на конкретном примере горного пейзажа со скачущим всадником.

Параллакс фона. Клик по сцене вызывает эффект фокусировки с размытием.

И так, для этой сцены было создано пять блоков объединенные одним общим контейнером. Каждый из этих блоков содержит класс с CSS свойством, указывающим на свое фоновое изображение. Кроме этого, для каждого блока установлено своё позиционирование фонового изображения.

Фоновые изображения PNG для параллакс эффекта

Картинки фоновых изображений PNG с прозрачными областями, используемых для создания параллакса фонов.

<div class="container">
    <div data-landscape class="sky"></div>
    <div data-landscape class="hill_distant"></div>
    <div data-landscape class="hill_near"></div>
    <div data-landscape class="ground"></div>
    <div data-rider class="rider"></div>
</div>

Атрибут data-landscape является триггером, с помощью которого скрипт определяет, что вот именно для этих блоков нужно менять позиционирование фона, то есть свойство background-position. В последнем блоке с атрибутом data-rider никаких изменений не производится, а там просто присутствует анимированное GIF-изображение с всадником.

.container {
    position: absolute;
    width: 100%;
    min-height: 787px;
    height:100%;
    overflow: hidden;
}

.container > div {
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    transition: background-position linear, transform ease, filter ease;
    transition-duration: inherit;
}

.container > div[data-landscape] {
    width: 200%;
    margin-left: -25%;
}

.container > div[data-rider] {
    width: 100%;
    margin-left: 0%;
}

.sky          {background: url(sky.png) 0 0 repeat-x;}
.hill_distant {background: url(mountains_2.png) left 0 top 29vh repeat-x;}
.hill_near    {background: url(mountains_1.png) 0 bottom repeat-x;}
.ground       {background: url(ground.png) 0 bottom repeat-x;}
.rider        {background: url(rider_hq.gif) left 50% bottom 120px no-repeat;}

В CSS для основного контейнера должно быть указано позиционирование отличное от static, чтобы можно было одинаково расположить и растянуть внутри него внутренние блоки с изображениями фонов. Для вложенных блоков в основной контейнер (.contain > div)потребуется указать абсолютное позиционирование, чтобы блоки смогли наложиться друг поверх друга. Помимо этого, для этих же блоков нужно указать линейный переход для свойств, которые мы будем изменять с помощью JavaScript достигая эффекта параллакса.

transition: background-position linear;

Также для внутренних блоков потребуется поставить и свойство transition-duration: inherit, то есть наследуемый, поскольку это свойство будет более грамотно устанавливать посредством скрипта родителю, ведь длительность CSS-перехода должна быть равна задержке следующей итерации изменения координат, что из скрипта контролировать проще.

Затем, каждому отдельному блоку необходимо указать фоновое изображение и координаты абсцисс начального положения фона и постоянного положения ординат, поскольку анимация исключительно горизонтальная и менять мы будем только координаты оси Х.

let container = document.querySelector('.container');
let coords = [];
let step = 250;
let timeout = 2000;

container.style.transitionDuration = (timeout/1000)+'s';

function parallax(){
    let landscapeLayers = container.querySelectorAll('[data-landscape]');
    landscapeLayers.forEach((block, key) => {
        coords[key] = parseInt(block.style.backgroundPositionX) || 0;
        block.style.backgroundPositionX = coords[key] + step * (key + 1)  * -1  + 'px';
    });

    setTimeout(() => {
        parallax();
    }, timeout);
}

parallax();

Перед объявлением и запуском рекурсивной функции нужно установить длительность выполнения CSS-перехода для контейнера, которое унаследуют дочерние блоки.

contain.style.transitionDuration = (timeout/1000)+'s';

Далее следует объявление рекурсивной функции parallax(), в которой выделяются внутренние блоки, для которых требуется менять положение фона (с селектором атрибута data-landscape), выполняя проход и пересчитывая координаты абсцисс фонового изображения. Обратите внимание, что для каждого последующего блока инкремент для координаты должен быть больше предыдущего, например, на один шаг, то есть на 250 пикселей. Для этого можно воспользоваться умножением на ключи коллекции key плюс единица. +1 потому что первый ключ коллекции равен нулю. Именно таким образом, когда «приближенный» фон движется быстрее, достигается эффект параллакса.

landscapeLayers.forEach((block, key) => {
    coords[key] = parseInt(block.style.backgroundPositionX) || 0;
    block.style.backgroundPositionX = coords[key] + step * (key + 1)  * -1  + 'px';
});

Завершается этот скрипт отсрочкой рекурсивного вызова следующей итерации данной функции.

Далее следует объявление рекурсивной функции parallax(), в которой выделяются внутренние блоки, для которых требуется менять положение фона (с селектором атрибута data-landscape), выполняя проход и пересчитывая координаты абсцисс фонового изображения. Обратите внимание, что для каждого последующего блока инкремент для координаты должен быть больше предыдущего, например, на один шаг, то есть на 250 пикселей. Для этого можно воспользоваться умножением на ключи коллекции key плюс единица. +1 потому что первый ключ коллекции равен нулю. Именно таким образом, когда «приближенный» фон движется быстрее, достигается эффект параллакса.

setTimeout(()=>{
    parallax();
}, timeout);

Ну и конечно же, запустить выполнение нужно отдельным вызовом функции.

parallax();

Параллакс по движению мыши

Давайте теперь рассмотрим, как сделать эффект параллакса, зависящий от движения мыши. Здесь же я разберу другой способ реализации параллакса основанный на свойстве CSS3 transform с функцией translate(). Именно это свойство будет меняться в зависимости от координат положения курсора мыши в окне браузера , заключенных в свойствах события мыши — clientX и clientY.

Параллакс по движению мыши. Клик по сцене вызывает эффект фокусировки.

И так, давайте воспроизведем этот пример практически. Как вы уже, вероятно, догадались, для облаков используются div-блоки с изображением. Так же, как и в первом примере параллакса фона, для главного контейнера установлено отличное от static позиционирование для того, чтобы внутренние блоки с облаками можно было свободно разместить в любом месте, используя абсолютное позиционирование. Для эффекта параллакса использовано четыре положения «отдаления» или «глубины», которые обозначены с помощью атрибута data-size в каждом блоке-облаке. Сценарий JavaScript будет считывать этот атрибут и назначать этим блокам соответствующие обозначениям свойства.

<div class="container">
    <div data-size="nano" class="cloud_1"> <img src="cloud.png" alt=""></div>
    <div data-size="nano" class="cloud_2"> <img src="cloud.png" alt=""></div>
    <div data-size="nano" class="cloud_3"> <img src="cloud.png" alt=""></div>
    <div data-size="nano" class="cloud_4"> <img src="cloud.png" alt=""></div>
    <div data-size="nano" class="cloud_5"> <img src="cloud.png" alt=""></div>
    <div data-size="nano" class="cloud_6"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_7"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_8"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_9"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_10"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_11"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_12"> <img src="cloud.png" alt=""></div>
    <div data-size="small" class="cloud_13"> <img src="cloud.png" alt=""></div>
    <div data-size="medium" class="cloud_14"> <img src="cloud.png" alt=""></div>
    <div data-size="medium" class="cloud_15"> <img src="cloud.png" alt=""></div>
    <div data-size="medium" class="cloud_16"> <img src="cloud.png" alt=""></div>
    <div data-size="medium" class="cloud_17"> <img src="cloud.png" alt=""></div>
    <div data-size="medium" class="cloud_18"> <img src="cloud.png" alt=""></div>
    <div data-size="large" class="cloud_19"> <img src="cloud.png" alt=""></div>
    <div data-size="large" class="cloud_20"> <img src="cloud.png" alt=""></div>
    <div data-size="large" class="cloud_21"> <img src="cloud.png" alt=""></div>
    <div data-size="large" class="cloud_22"> <img src="cloud.png" alt=""></div> 
</div>
.container {
    width: 100%;
    height: 100%;
    background: #fff;
    position: absolute;
    overflow: hidden;
}

.container > div {
    position: absolute;
}

.container > div img {
    width: 100%;
}

[data-size="nano"] {width: 2.5%; filter: blur(9px);}
[data-size="small"] {width: 5%; filter: blur(6px);}
[data-size="medium"] {width: 10%; filter: blur(3px);}
[data-size="large"] {width: 15%;}

.cloud_1 {top:41%; left: 38%;}
.cloud_2 {top:42%; left: 81%;}
.cloud_3 {top:23%; left: 19%;}
.cloud_4 {top:71%; left: 54%;}
.cloud_5 {top:37%; left: 60%;}
.cloud_6 {top:5%; left: 67%;}
.cloud_7 {top:15%; left: 35%;}
.cloud_8 {top:41%; left: 25%;}
.cloud_9 {top:44%; left: 68%;}
.cloud_10 {top:65%; left: 35%;}
.cloud_11 {top:30%; left: 90%;}
.cloud_12 {top:80%; left: 5%;}
.cloud_13 {top:87%; left: 80%;}
.cloud_14 {top:70%; left: 62%;}
.cloud_15 {top:5%; left: 75%;}
.cloud_16 {top:75%; left: 20%;}
.cloud_17 {top:8%; left: 8%;}
.cloud_18 {top:41%; left: 45%;}
.cloud_19 {top:2%; left: 47%;}
.cloud_20 {top:75%; left: 38%;}
.cloud_21 {top:55%; left: 81%;}
.cloud_22 {top:41%; left: 2%;}

Также атрибут data-size используется как селектор, чтобы указать соответствующие размеры блоков для имитации пространственной перспективы.

Далее для каждого блока требуется указать уникальное положение на экране, поэтому для каждого из них прописан стилевой класс с координатами позиционирования.

let container = document.querySelector('.container');
let clouds = container.querySelectorAll('.container > div');
let percentY, percentX;

container.addEventListener('mousemove', (event) => {
        
    percentX = ((event.clientX - container.clientWidth) * 100 / container.clientWidth) + ((event.clientX) * 100 / container.clientWidth);
    percentY = ((event.clientY - container.clientHeight) * 100 / container.clientHeight) + ((event.clientY) * 100 / container.clientHeight);

    clouds.forEach(cloud => {
        switch (cloud.dataset.size) {
            case 'nano':
                cloud.style.transform = 'translate('+percentX/-1.5+'%, '+percentY/-1.5+'%)';                
                break;
            case 'small':
                cloud.style.transform = 'translate('+percentX/-1.2+'%, '+percentY/-1.2+'%)';                
                break;
            case 'medium':
                cloud.style.transform = 'translate('+percentX/-0.9+'%, '+percentY/-0.9+'%)';                
                break;
            case 'large':
                cloud.style.transform = 'translate('+percentX/-0.5+'%, '+percentY/-0.5+'%)';                
                break;
        }
    });
});

Как вы можете видеть, JavaScript сценарий параллакса по движению мыши достаточно простой. Прежде всего следует получить объекты, с которыми мы будем работать: контейнер и блоки с облаками.

let container = document.querySelector('.container');
let clouds = container.querySelectorAll('.container > div');
let percentY, percentX;

Затем следует установить «слушатель» на событие движение мыши внутри основного контейнера.

container.addEventListener('mousemove', (event) => {...});

Внутри него записан сценарий, который будет срабатывать при каждом смещение курсора, а именно, пересчитывать положение облаков в процентные значения (процентами проще оперировать, так как они не зависят от размеров окна), которые находятся в прямой зависимости от значений полодения курсора event.clientX и event.clientY...

percentX = ((event.clientX - container.clientWidth) * 100 / container.clientWidth) + ((event.clientX) * 100 / container.clientWidth);
percentY = ((event.clientY - container.clientHeight) * 100 / container.clientHeight) + ((event.clientY) * 100 / container.clientHeight);

...а далее, с помощью прохода по коллекции облаков, назначать им новые значения положения в CSS функции translate(), соответственно слову указанному в атрибуте data-size. Для каждого вида блоков-облаков добавляется свой делитель, для того, чтобы притормозить или ускорить движение.

clouds.forEach(cloud => {
    switch (cloud.dataset.size) {
        case 'nano':
            cloud.style.transform = 'translate('+percentX/-1.5+'%, '+percentY/-1.5+'%)';                
            break;
        case 'small':
            cloud.style.transform = 'translate('+percentX/-1.2+'%, '+percentY/-1.2+'%)';                
            break;
        case 'medium':
            cloud.style.transform = 'translate('+percentX/-0.9+'%, '+percentY/-0.9+'%)';                
            break;
        case 'large':
            cloud.style.transform = 'translate('+percentX/-0.5+'%, '+percentY/-0.5+'%)';                
            break;
    }
});

Вот и весь сценарий JavaScript. И хотя вы можете наблюдать дополнительный эффект фокусировки по событию клика мыши, в описании скрипта его нет, поскольку в цели данного урока это не входило. Он добавлен просто для украшения, ведь он так хорошо вписывается в конструкцию с эффектом параллакса. Но вы можете с ним ознакомиться, посмотрев в исходный код страницы или попробовать сделать сами.

Надеюсь, урок был познавательным для вас. Желаю вам удачи!