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

Живой демонстрационный пример (CodePen): эффект учитывает настройки reduced motion, поэтому для просмотра его нужно включить. На момент написания статьи работает в Chrome и Safari.

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

Структура HTML

Три вложенных уровня: родительский контейнер (.opposing-columns), дочерние колонки (.opposing-column) и элементы внутри каждой колонки (.opposing-item).

<div class="opposing-columns">
  <div class="opposing-column">
    <div class="opposing-item">...</div>
    <div class="opposing-item">...</div>
  </div>
  <div class="opposing-column">
    <div class="opposing-item">...</div>
  </div>
  <div class="opposing-column">
    <div class="opposing-item">...</div>
  </div>
</div>

Вот и всё. Остальное делает CSS.

Адаптивная настройка

Эффект имеет смысл только на больших экранах с достаточным горизонтальным пространством. Оборачиваем всё в медиа-запрос:

@media screen and (width >= 50rem) {
  .opposing-columns {
    display: flex;
    gap: 2rem;
    max-inline-size: min(90dvi, 50rem);
    margin-inline: auto;
  }
}

Создание иллюзии маскирования

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

Сначала задаем переменную с цветом фона в :root:

:root {
  --opposing-bg: lightcyan;
  --opposing-mask: 3rem;
  background-color: var(--opposing-bg);
}

Затем применяем :before и :after к родительскому элементу с тем же цветом фона и z-index: 1, чтобы они находились над колонками:

.opposing-columns {
  position: relative;
  margin-block: var(--opposing-mask, 3rem);

  &:before,
  &:after {
    content: "";
    position: absolute;
    inset-inline: 0;
    block-size: calc(var(--opposing-mask) * 3);
    pointer-events: none;
    z-index: 1;
  }
}

Теперь градиенты:

  • Верхний псевдоэлемент (:before): от сплошного фона к прозрачному, сверху вниз.
  • Нижний псевдоэлемент (:after): от прозрачного к сплошному фону, снизу вверх.
.opposing-columns {
  &:before {
    background-image: linear-gradient(
      to bottom,
      var(--opposing-bg) var(--opposing-mask),
      transparent
    );
    inset-block-start: calc(var(--opposing-mask) * -1);
  }

  &:after {
    background-image: linear-gradient(
      to top,
      var(--opposing-bg) var(--opposing-mask),
      transparent
    );
    inset-block-end: calc(var(--opposing-mask) * -1);
  }
}

Элементы при скролле скользят под эти маски, создавая чистый эффект затухания на верхнем и нижнем краях контейнера.

Практические соображения для бизнеса на Кипре и в ЕС

  • Производительность: анимации на основе скролла выполняются в потоке композитора — никаких зависаний из-за JavaScript. Это важно для мобильных пользователей с 4G в Лимассоле или Никосии.
  • GDPR и доступность: демонстрационный пример учитывает prefers-reduced-motion. Всегда добавляйте эту проверку, чтобы не нарушать директивы ЕС по доступности.
  • Многоязычные сайты (EN/RU/EL): эта CSS-техника работает независимо от переключения языков или RTL-верстки (используйте inset-inline и inset-block, как мы сделали).
  • Оценка бюджета: на подобный кастомный компонент уходит 2–4 часа разработки. В сочетании с полным сайтом (5–10 страниц) типичная стоимость в кипрской веб-студии составляет €1,500–€3,500.