Pull to refresh
834.09
OTUS
Цифровые навыки от ведущих экспертов

Эффективный параллакс

Reading time 8 min
Views 8.4K
Original author: Paul Lewis, Robert Flack

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

Коротко

  • Не используйте для создания параллакс-анимации прокрутку событий или background-position.

  • Для создания более четкого параллакс-эффекта используйте 3D-трансформации средствами CSS.

  • Для Mobile Safari используйте position: sticky, чтобы обеспечить распространение эффекта параллакса.

Если вам нужно готовое решение, заходите в репозиторий UI Element Samples на GitHub и берите Parallax JS хелпер! В репозитории GitHub вы можете увидеть демонстрацию параллакс-прокрутки в реальном времени.

Проблемы с параллаксами

Для начала давайте рассмотрим два распространенных способа достижения эффекта параллакса и, в частности, почему они не подходят для наших целей.

Плохой вариант: использование событий при прокрутке

Ключевым требованием параллакса является то, что он должен быть связан со скроллингом; при каждом изменении положения прокрутки страницы позиция параллакс-элемента должна обновляться. Звучит просто, но важным механизмом современных браузеров является их способность работать асинхронно. В нашем конкретном случае это относится к событиям прокрутки. В большинстве браузеров события прокрутки передаются по принципу "best-effort" ("наилучшая попытка") и не гарантируется, что они будут выполнены для каждого кадра анимации скроллинга!

Эта важная деталь информирует нас о том, почему мы должны избегать JavaScript-решений, перемещающих элементы на основе событий прокрутки: JavaScript не гарантирует, что параллакс будет соответствовать положению прокручиваемой страницы. В ранних версиях Mobile Safari события прокрутки доставлялись фактически в конце скроллинга, что делало невозможным создание скролл-эффекта на основе JavaScript. Более поздние версии действительно передают события прокрутки во время анимации, но, как и в Chrome, по принципу - "наилучшая попытка". Если основной поток занят какой-либо другой работой, события прокрутки сразу не будут доставлены, а значит, эффект параллакса будет потерян.

Плохой вариант: обновление background-position

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

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

CSS в 3D

Scott Kellum и Keith Clark проделали значительную работу в области использования CSS 3D для обеспечения параллакс-движения, и техника, которую они применяют, заключается в следующем:

  • Настройте содержащийся элемент на прокрутку с overflow-y: scroll (и, возможно, overflow-x: hidden).

  • К этому же элементу примените значение perspective, а для perspective-origin установите значение top left или 0 0.

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

CSS для этого подхода выглядит следующим образом:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Что предполагает наличие HTML-сниппета, подобного этому:

<div class="container">
    <div class="parallax-child"></div>
</div>

Настройка масштаба для перспективы

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

В приведенном выше коде перспектива равна 1px, а расстояние по Z у parallax-child равно -2px. Это означает, что элемент должен быть увеличен в 3 pаза, что видно из значения, введенного в код: scale(3).

Для любого содержимого, к которому не применяется значение translateZ, можно подставить нулевое значение. Это означает, что масштаб равен (переспектива — 0) / перспектива, и в итоге дает значение 1, то есть масштаб не увеличивается и не уменьшается. Достаточно удобно, на самом деле.

Как работает этот подход

Важно понять, почему это работает, поскольку мы собираемся использовать эти знания в ближайшее время. Прокрутка — это фактически преобразование, поэтому ее можно ускорить; в основном она включает в себя перемещение слоев с помощью GPU. В обычном скролле, который не имеет понятия перспективы, прокрутка происходит в пропорции 1:1 при сравнении прокручиваемого и его дочерних элементов. Если вы прокручиваете элемент вниз на 300px, то его дочерние элементы трансформируются вверх на ту же величину: 300px.

Однако применение значения перспективы к прокручиваемому элементу нарушает этот процесс; оно изменяет матрицы, лежащие в основе трансформации прокрутки. Теперь прокрутка на 300px может переместить дочерние элементы только на 150px, в зависимости от выбранных значений perspective и translateZ. Если у элемента значение translateZ равно 0, он будет прокручиваться со скоростью 1:1 (как и раньше), но дочерний элемент, сдвинутый по Z от начала перспективы, будет прокручиваться с другой скоростью! В итоге: параллакс-движение. И, что очень важно, это обрабатывается как часть внутреннего механизма прокрутки браузера автоматически, то есть нет необходимости слушать scroll события или изменять background-position.

Ложка дегтя в бочке меда: Mobile Safari

У каждого эффекта есть свои оговорки, и одна из них касается трансформаций — сохранение 3D-эффектов для дочерних элементов. Если в иерархии между элементом с перспективой и его дочерними элементами с параллаксом существуют еще какие-нибудь элементы, 3D перспектива "сплющивается", то есть эффект теряется.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

В приведенном выше HTML, .parallax-container будет новым, он фактически сплющит значение perspective, и мы потеряем эффект параллакса. Решение, в большинстве случаев, довольно простое: добавьте transform-style: preserve-3d к элементу, что заставит его распространять любые 3D-эффекты (например, наше значение перспективы), которые были применены дальше по дереву.

.parallax-container {
  transform-style: preserve-3d;
}

Однако в случае с Mobile Safari все немного сложнее. Применение overflow-y: scroll к элементу-контейнеру технически работает, но ценой отсутствия способности прокрутки скролл-элемента. Решением является добавление -webkit-overflow-scrolling: touch, но это также приведёт к сплющиванию perspective, и мы не получим никакого параллакса.

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

position: sticky на помощь!

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

Липко позиционированный блок размещается аналогично относительно позиционированному блоку, но смещение рассчитывается применительно к ближайшему предку с прокручиваемым блоком или к области просмотра, если у предка нет прокручиваемого блока. — Модуль позиционированного макета CSS Уровень 3

На первый взгляд это может показаться не очень важным, но ключевым моментом в этом предложении является указание на то, как именно рассчитывается прилипание элемента: "смещение вычисляется по отношению к ближайшему предку с прокручиваемым полем". Другими словами, расстояние, на которое нужно переместить прилипший элемент (чтобы он оказался прикрепленным к другому элементу или к области просмотра), рассчитывается до применения любых других преобразований, а не после. Это означает, что, как и в предыдущем примере с прокруткой, если смещение было рассчитано на 300px, появляется новая возможность использовать перспективы (или любое другое преобразование) для манипулирования значением смещения на 300px до того, как оно будет применено к любым прилипшим элементам.

Применяя position: -webkit-sticky к параллакс-элементу, мы можем эффективно "отменить" эффект сплющивания от -webkit-overflow-scrolling: touch. Это гарантирует, что параллакс-элемент ссылается на ближайшего предка с прокручиваемым полем, которым в данном случае является .container. Затем, как и раньше, к .parallax-container применяется значение perspective, которое изменяет рассчитанное смещение прокрутки и создает эффект параллакса.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Это позволяет восстановить эффект параллакса для Mobile Safari, что является отличной новостью для всех!

Предостережения по позиционированию "липких" элементов

Однако здесь есть разница: position: sticky действительно изменяет механику параллакса. Липкое позиционирование пытается прикрепить элемент к прокручиваемому контейнеру, в то время как нелипкая версия этого не делает. Это означает, что параллакс с липким позиционированием становится обратным параллаксу без него:

  • С position: sticky, чем ближе элемент к z=0, тем меньше он перемещается.

  • Без position: sticky, чем ближе элемент к z=0, тем больше он движется.

Если все это представляется немного абстрактным, посмотрите на демонстрацию от Robert Flack, которая показывает, как по-разному ведут себя элементы с липким (sticky) позиционированием и без него. Чтобы увидеть разницу, вам понадобится Chrome Canary (на момент написания статьи это версия 56) или Safari.

Это параллакс с использованием перспективы на элементе переполнения (overflow). Начало перспективы находится за пределами скроллера. Это означает, что для того, чтобы элемент параллакса не двигался, он должен находиться бесконечно далеко.

  • начало перспективы (элемент прокрутки).

Это параллакс с использованием позиционного прилипания. Начало перспективы находится внутри скроллера, поэтому он перемещается вверх по мере прокрутки.

Чтобы элемент параллакса двигался в пропорции 1:1 с прокручиваемым содержимым, он должен быть бесконечно далеко.

Демонстрация от Robert Flack, показывающая, как position: sticky влияет на параллакс-прокрутку.

Различные ошибки и обходные пути

Однако, как и везде, здесь все еще есть неровности, которые нужно сгладить:

  • Поддержка технологии прилипания непостоянна. В Chrome она все еще реализуется, в Edge отсутствует полностью, а в Firefox есть ошибки рисования, когда липкость сочетается с трансформациями перспективы. В таких случаях стоит внести небольшой код, чтобы добавлять position: sticky (версия с префиксом -webkit-) при необходимости, и это только для Mobile Safari.

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

  • "Содержимое страницы только что стало огромным!". Многие браузеры учитывают масштаб при определении размера содержимого страницы, но, к сожалению, Chrome и Safari не учитывают перспективу. Поэтому если к элементу применен масштаб, скажем, 3x, вы вполне можете увидеть полосы прокрутки и тому подобное, даже если после применения perspective элемент будет иметь размер 1x. Эту проблему можно обойти, масштабируя элементы из правого нижнего угла (с помощью transform-origin: bottom right), и это работает, поскольку приводит к тому, что элементы больших размеров вырастают в "отрицательную область" (обычно левую верхнюю) прокручиваемой области; прокручиваемые области никогда не позволяют видеть или прокручивать содержимое в отрицательной области.

Заключение

Параллакс — интересный эффект в случае продуманного использования. Как вы видите, его можно реализовать таким образом, чтобы он был высокопроизводительным, связанным с прокруткой и кроссбраузерным. Поскольку для получения желаемого эффекта потребуется немного математических выкладок и небольшое количество бойлерплейта, мы подготовили небольшую библиотеку-хелпер и пример, которые вы можете найти в нашем GitHub-репозитории UI Element Samples.


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

Tags:
Hubs:
+4
Comments 1
Comments Comments 1

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS