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

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

Разметка состоит из дива контейнера, изображения и текста обёрнутого в ещё один 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 событиями на движение мыши может быть относительно простым способом для достижения эффекта параллакса, с небольшим количеством математики.