Простой параллакс на 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 трансформаций:
- оба расстояния умножаются на -1 (точение положительное расстояние в отрицательное, а отрицательное в положительное)
- затем результат делится на 12, чтобы уменьшить соотношение количества движения между мышью и изображением
Следует отметить важный момент, я настоятельно не рекомендую использовать эту технику для элементов пользовательского интерфейса: попытка кликнуть по "движущейся мишени" может вызвать значительные трудности у пользователей с проблемной моторикой и зрением. Одно из возможных решений этой проблемы обсудим далее в статье.
Когда мы вращаем готовой перемещаются не только предметы на переднем плане, объекты на заднем плане так-же перемещаются в нашем восприятии пространства. Представьте объект в нескольких метрах перед стеной, поворачивая голову на право вы обнаружите, что перспектива меняется у обоих.
Для воссоздания данного эффекта я написал код, который тк-же будет перемещать фон у главного блока:
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 событиями на движение мыши может быть относительно простым способом для достижения эффекта параллакса, с небольшим количеством математики.