Введение
Параллакс-эффект — визуальная техника, при которой разные слои фона движутся с разной скоростью относительно друг друга при скролле или другом взаимодействии, создавая иллюзию глубины и трёхмерности на плоском экране. В статье разберём реализацию многослойного параллакс-эффекта для фонового изображения экрана и для прокручиваемого списка.
Концепция
Базовая идея параллакса — несколько визуальных слоёв (обычно изображений), расположенных друг над другом, каждый со своим собственным коэффициентом скорости движения относительно общего управляющего параметра (позиция скролла, наклон устройства, позиция курсора) — слои, представляющие «далёкие» объекты, движутся медленнее (создавая иллюзию большого расстояния), а «близкие» — быстрее, что соответствует тому, как воспринимается реальная глубина при движении наблюдателя в физическом мире.
Пример кода: Параллакс-эффекты и многослойная
// ParallaxBackground.qml — многослойный фон с разной скоростью движения слоёв
import QtQuick
Item {
id: root
property real scrollOffset: 0 // управляющий параметр — например, позиция скролла основного контента
// Самый дальний слой — движется медленнее всего (коэффициент 0.2)
Image {
source: "qrc:/backgrounds/sky.png"
width: parent.width * 1.5
x: -root.scrollOffset * 0.2
fillMode: Image.PreserveAspectCrop
anchors.verticalCenter: parent.verticalCenter
}
// Средний слой — облака, движутся быстрее неба, но медленнее переднего плана
Image {
source: "qrc:/backgrounds/clouds.png"
width: parent.width * 1.8
x: -root.scrollOffset * 0.5
fillMode: Image.PreserveAspectCrop
anchors.verticalCenter: parent.verticalCenter
}
// Ближний слой — горы/деревья на переднем плане, движутся быстрее всего
Image {
source: "qrc:/backgrounds/foreground.png"
width: parent.width * 2.2
x: -root.scrollOffset * 0.9
fillMode: Image.PreserveAspectCrop
anchors.bottom: parent.bottom
}
}
// Привязка параллакса к реальной прокрутке списка контента
import QtQuick
Item {
ParallaxBackground {
id: background
anchors.fill: parent
scrollOffset: contentListView.contentX // синхронизация с горизонтальной прокруткой списка
}
ListView {
id: contentListView
anchors.fill: parent
orientation: ListView.Horizontal
model: contentModel
delegate: ContentCard { }
}
}
// Параллакс, управляемый наклоном устройства через QtSensors (для мобильных платформ)
import QtQuick
import QtSensors
Item {
Accelerometer {
id: accelerometer
active: true
onReadingChanged: {
// Наклон устройства транслируется в смещение параллакс-фона
background.scrollOffset = -reading.x * 50;
}
}
ParallaxBackground {
id: background
anchors.fill: parent
}
}
Пояснения к коду
ParallaxBackground определяет три слоя изображений с разными коэффициентами умножения на общий управляющий параметр scrollOffset (0.2 для самого дальнего слоя, 0.9 для самого близкого) — поскольку все три слоя получают одно и то же значение scrollOffset, но умножают его на разные коэффициенты, их визуальное смещение оказывается пропорционально разным, что и создаёт ощущение глубины. Каждый слой шире самого видимого экрана (width: parent.width * 1.5 и больше) — это необходимый запас, чтобы при смещении слоя в любую сторону на экране не появлялись пустые области без изображения по краям.
Второй пример показывает практическое применение — привязка scrollOffset к реальной позиции прокрутки ListView (contentX) синхронизирует параллакс-эффект с тем, как пользователь физически взаимодействует со списком контента, создавая ощущение, что фон является частью одной согласованной «глубокой» сцены, а не отдельным, не связанным с прокруткой декоративным элементом. Третий пример показывает альтернативный источник управляющего параметра — акселерометр устройства через модуль QtSensors, создающий параллакс-эффект, реагирующий на физический наклон устройства, типичный для некоторых мобильных игр и интерактивных заставок.
Подводные камни
- Недостаточная ширина слоёв фона относительно максимально возможного диапазона смещения приводит к появлению пустых, незаполненных областей по краям экрана при достижении предельных значений
scrollOffset— нужный запас ширины каждого слоя зависит от максимального ожидаемого диапазона значений управляющего параметра и соответствующего коэффициента слоя, и это нужно явно рассчитывать (или щедро завышать с запасом) для каждого конкретного сценария использования, а не выбирать произвольные значения ширины. - Параллакс-эффект, синхронизированный с прокруткой основного контента, может конфликтовать с инерционной прокруткой (flick)
ListView, если обновление позиции фона выполняется недостаточно часто или с заметной задержкой относительно реальной позиции списка — для плавного, без визуального «отставания» параллакса важно, чтобы привязкаscrollOffsetкcontentXбыла прямой, реактивной привязкой (как в примере), а не опосредованной через промежуточные таймеры или асинхронные обновления, вносящие дополнительную задержку. - Несколько крупных, полупрозрачных или сложных изображений, рендерящихся одновременно как слои параллакса, особенно при больших размерах (с запасом по ширине) создают значительную нагрузку на память и рендеринг — для embedded или менее мощных мобильных устройств (основной цикл статей про embedded) стоит явно ограничивать количество и разрешение слоёв параллакса, возможно, заменяя часть слоёв на более простые, программно генерируемые градиенты вместо полноценных растровых изображений.
- Параллакс-эффект, управляемый акселерометром, без фильтрации «шума» необработанных показаний датчика может создавать дрожащее, нервное визуальное движение фона вместо плавного, приятного эффекта — необработанные показания акселерометра обычно содержат высокочастотный шум от вибрации устройства и движения руки пользователя, и для приятного визуального результата практически всегда нужна программная фильтрация (простое экспоненциальное усреднение или более сложный фильтр) перед использованием значения датчика напрямую для управления визуальным эффектом.