Параллакс эффект

Фоновое изображение движется медленнее контента при скролле — CSS background-attachment: fixed или JS через transform: translateY.

Задача

Героическая секция сайта должна создавать глубину: фон скользит медленнее, чем текст поверх него. Нужно реализовать это без тяжёлых библиотек.

Решение

Способ 1 — CSS background-attachment: fixed (простой)

<section class="parallax-hero">
  <div class="parallax-hero__content">
    <h1>Заголовок</h1>
    <p>Описание</p>
  </div>
</section>
.parallax-hero {
  background-image: url('hero.jpg');
  background-attachment: fixed; /* ключевое свойство */
  background-size: cover;
  background-position: center;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.parallax-hero__content {
  text-align: center;
  color: #fff;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}

Ограничение: не работает корректно в iOS Safari и некоторых браузерах на мобильных.

Способ 2 — JS через transform (кроссбраузерный)

<div class="parallax-wrap">
  <div class="parallax-bg" id="parallaxBg" style="background-image: url('hero.jpg')"></div>
  <div class="parallax-content">
    <h1>Заголовок</h1>
  </div>
</div>
.parallax-wrap {
  position: relative;
  height: 100vh;
  overflow: hidden;
}

.parallax-bg {
  position: absolute;
  inset: -20%; /* запас для сдвига */
  background-size: cover;
  background-position: center;
  will-change: transform;
}

.parallax-content {
  position: relative;
  z-index: 1;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}
const bg = document.getElementById('parallaxBg');
const section = bg.closest('.parallax-wrap');

function updateParallax() {
  const rect = section.getBoundingClientRect();

  // Смещение: от -10% до +10% относительно позиции секции
  const progress = rect.top / window.innerHeight; // -1..1
  const offset = progress * 15; // 15% скорость параллакса

  bg.style.transform = `translateY(${offset}%)`;
}

window.addEventListener('scroll', () => {
  requestAnimationFrame(updateParallax);
}, { passive: true });

Ключевые моменты

  • will-change: transform — создаёт отдельный GPU-слой; анимация не вызывает repaint.
  • requestAnimationFrame в scroll-обработчике — синхронизация с кадром рендера.
  • { passive: true } — браузер знает что preventDefault не вызывается, скролл не тормозит.
  • CSS-вариант проще, но ненадёжен на iOS; JS-вариант работает везде.

Когда НЕ использовать

  • На мобильных устройствах — может вызывать укачивание (vestibular disorders); используй @media (prefers-reduced-motion: reduce).
  • Для многих параллакс-элементов — производительность падает; рассмотри CSS-only или Intersection Observer.

Связанные рецепты / темы