Lab / JS

Простой параллакс на JavaScript и CSS Transforms

В последнее время большое колличество сайтов начили повсемемтное употребеление паралакса повешенного на движение мыши в зеркальном отражении. Мне стало интересно создать реализацию на чистом JavaScript и современном CCS. Что у меня получилось вы можете посмотреть в этом примере:

See the Pen <a data-cke-saved-href='http://codepen.io/mbogrov/pen/WoEYGq/' href='http://codepen.io/mbogrov/pen/WoEYGq/'>Simple parallax with vanilla JS</a> by mbogrov (<a data-cke-saved-href='http://codepen.io/mbogrov' href='http://codepen.io/mbogrov'>@mbogrov</a>) on <a data-cke-saved-href='http://codepen.io' href='http://codepen.io'>CodePen</a>.

Разметка состоит из дива контейнера, изображения и текста обёрнутого в ещё один div.

<div id="boxercontainer">
 <img src="boxer.png" alt>
 <div>
 <h1>Что такое Lorem Ipsum?</h1>
 <p>Lorem Ipsum - это текст-"рыба", часто используемый в печати и вэб-дизайне...
 </div>
</div>

В качестве изображения я использовал офисного сотрудника в PNG на прозрачном фоне. Фоновое изображение которые мы подставляем и центрируем в #boxercontainer должно быть немного больше, чем сам блок (помните как мы делали паралакс обои с выходом IOS7?). Теперь создаём стили для этого контейнера:

#boxercontainer {
 width: 80%;
 max-width: 900px;
 margin: 0 auto;
 background-image: url(concrete-background.jpg);
 position: relative;
 padding-bottom: 45%;
 background-size: 120% 120%;
 background-position: 50% 50%;
 overflow: hidden;
 min-height: 650px;
}

Теперь давайте создадим внутренний div в котоый поместим текстовое содержимое. Зададим этому диву полупрозрачный тёмный фон, для лучшего контраста с фоном.

#boxercontainer div {
 position: absolute;
 width: 60%;
 left: 20px;
 top: 20px;
 border: 1px solid #fff;
 padding: 2rem;
 background: rgba(0,0,0,0.2);
}

Изображение нашего офисного сотрудника мы тоже спозиционируем абсолютно (использую трюк абсоютного позиционирования внутри блока с относительным), таким образом, что бы нижняя часть немного выходила за облать которую отрезает overflow: hodden; в родительском элементе. Так-же применяем drop-shadow фильтр к изображению, что бы нарисовать его тень, без создания дополнительного изображения.

#boxercontainer img { 
 position: absolute;
 bottom: -35px;
 right: 50px;
 width: 40%;
 filter: drop-shadow(-200px 200px 50px #000);
 padding: 1rem;
 z-index: 2;
}

Значение z-index: 2 распологает нашего офисного сотрудника над текстом, но тень которая падает от него, то-же попадает теперь на чекст, то конечно-же нас не устраивает, для исправления ситуации я задал z-index: 3 для параграфов:

#boxercontainer p {
 position: relative;
 z-index: 3;
}

Благодаря более высокому значением z-index у параграфа тень теперь за текст.


Основная идея параллакса эффекта найти центр подвижного элемента. Расстояние от курсора от этого центра будет смещать элемент на одинаковое расстояние в противоположном направлении:


Чтобы это произошло, скрипт начинается с определения элементов, учитывая тот факт, что ID автоматически превращяется в ссылку в JavaScript с помощью querySelector для изображения:

const boxer = boxercontainer.querySelector("img"),
maxMove = boxercontainer.offsetWidth / 30,
boxerCenterX = boxer.offsetLeft + (boxer.offsetWidth / 2),
boxerCenterY = boxer.offsetTop + (boxer.offsetHeight / 2);

maxMove — это максимальная дистанция на которую мы позволим удатяться изображению при наведении. Таким образом мы сможем ограничить свободное перемещение изображения по всех странице.

Так-же нам необходимо определить положение мыши в boxercontainer, для этого используйте следующую функцию:

function getMousePos(xRef, yRef) {
 let panelRect = boxercontainer.getBoundingClientRect();
 return {
 x: Math.floor(xRef - panelRect.left) / 
 (panelRect.right - panelRect.left)*boxercontainer.offsetWidth,
 y: Math.floor(yRef - panelRect.top) / 
 (panelRect.bottom - panelRect.top) * boxercontainer.offsetHeight
 };
}

Фээект и как они обычно реагирует на движение мыши по странице:

document.body.addEventListener("mousemove", function(e) {
 let mousePos = getMousePos(e.clientX, e.clientY),
 distX = mousePos.x - boxerCenterX,
 distY = mousePos.y - boxerCenterY;
 if (Math.abs(distX) < 500 && distY < 200) {
 boxer.style.transform = 
 "translate("+(-1 * distX) / 12 + "px," + (-1 * distY) / 12 + "px)";
 }
})

distX — это расстояние по горизонтали между текущим положением мыши и начальный центр нашего изображения. distY — расстояние по вертикали. Если вертикальная разница (положительная или отрицательная) меньше, чем 500px, а расстояние по горизонтали меньше, чем 200px, то мы перемещаем изображение с помощью CSS трансформаций:

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

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

Для воссоздания данного эффекта я написал код, который тк-же будет перемещать фон у главного блока:

boxercontainer.style.backgroundPosition = 
 `calc(50% + ${distX / 50}px) calc(50% + ${distY / 50}px)`;

Что бы фон то-же двигался я воспользовался свойством calc, что бы произвести движение фона от его центральной точки позиционирования. (Я также использую литералы шаблона, чтобы сделать конкатенацию проще).

Как правило эфект параллакса отлично смотриться на больших и средних экранах, но далеко не на каждом экране с маленькой блощатьсяю есть смыл его применять. Поскольку эффект создается с помощью JavaScript, мы должны определить ширину viewport на JS при помощи matchMedia:

let fluidboxer = window.matchMedia("(min-width: 726px)");

Если условия для перемещения изображения и фона изменяются:

if (Math.abs(distX) < 500 && distY < 200 && fluidboxer.matches) { … }

Можно создать несколько вариаций при помощи CSS @media выражений, чтобы изменить дизайн при меньших разрешениях.

P.S.

Эфект параллакса может вызывать неприятные ощущения и побочные эффекты вплоть до рвоты у пользователей с нарушениями вестибюлярного аппарата, по этому позаботтесь о том, что бы посетители со включеным флагом "уменьшение движения" в браузерах получали страницу с отключенным параллаксом. Это достаточно просто реализуется через меди выражения. Подробности гуглите по запросу: “reduce motion” user setting.

Заключение

Использование CSS трансформаций с JavaScript событиями на движение мыши может быть относительно простым способом для достижения эффекта параллакса, с небольшим количеством математики.