Уменьшение заголовка при прокрутке

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

Рекомендации по внедрению

Вот как создать уменьшающийся заголовок при прокрутке:

  1. Создайте фиксированный заголовок. Начните с элемента заголовка, который закреплен в верхней части страницы и имеет заданную высоту.

    <header>HEADER</header>
    
    header {
      position: fixed;
      height: 200px;
      top: 0;
      left: 0;
      right: 0;
    }
    
  2. Определите анимацию уменьшения. Создайте CSS-анимацию, которая изменяет высоту заголовка.

    @keyframes shrink {
      to {
        height: 50px;
      }
    }
    
  3. Примените анимацию и временную шкалу прокрутки. Прикрепите анимацию к заголовку и используйте функцию scroll(), чтобы связать ее с положением прокрутки документа.

    header {
      animation: shrink auto linear both;
      animation-timeline: scroll(block root);
    }
    
  4. Задайте свойство animation-range. С помощью свойства animation-range укажите расстояние прокрутки, на котором должна воспроизводиться анимация. Например, чтобы уменьшить заголовок на первых 150 пикселях прокрутки, используйте animation-range: 0px 150px;.

    header {
      animation-range: 0px 150px;
    }
    

Совет. Чтобы заголовок не перекрывал контент, добавьте в body (или контейнер основного контента) свойство padding-top, равное исходной высоте заголовка.

Совет. Чтобы контент страницы прокручивался синхронно с уменьшением заголовка, задайте для свойства animation-range-end значение, равное разнице между начальным и конечным размерами. Это гарантирует, что анимация завершится точно в тот момент, когда заголовок достигнет своего окончательного размера. В этом примере заголовок уменьшается с 200px до 50px, поэтому для animation-range-end задано значение 150px.

Пример кода

@keyframes shrink {
  to {
    height: 50px;
  }
}

header {
  animation: shrink auto linear both;
  animation-timeline: scroll(block root);
  animation-range: 0px 150px;
}

Рекомендации

При использовании анимации, управляемой прокруткой, важно следовать нескольким рекомендациям, чтобы обеспечить плавность и доступность:

  • РЕКОМЕНДУЕТСЯ включить обнаружение функций. Не все браузеры поддерживают анимацию, управляемую прокруткой. Используйте @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)), чтобы проверить поддержку и предоставить резервный вариант для браузеров, которые ее не поддерживают.
    • Проверка (animation-range: 0% 100%) ОБЯЗАТЕЛЬНО должна быть включена, чтобы отфильтровать браузеры с частичной поддержкой.
    • НЕ ИСПОЛЬЗУЙТЕ пакет scroll-timeline-polyfill для резервной стратегии, так как он неполный и имеет много известных проблем.
    • Если анимация используется только в декоративных целях, выберите прогрессивное улучшение и НЕ добавляйте резервный вариант.
  • Уважайте предпочтения пользователей. Некоторые пользователи предпочитают, чтобы на веб-страницах было меньше движения. Используйте медиазапрос prefers-reduced-motion, чтобы отключить или уменьшить анимацию для этих пользователей.
  • РЕКОМЕНДУЕТСЯ анимировать только эффективные свойства CSS. Чтобы анимация была максимально плавной, анимируйте свойства, которые могут обрабатываться потоком компоновщика браузера, например transform и opacity. Анимация других свойств, таких как width или height, может привести к проблемам производительности.
  • Правильно указывайте порядок объявления. При использовании сокращенного свойства animation объявляйте animation-timeline и animation-range после него, чтобы сокращенное свойство не сбрасывало временную шкалу.

При использовании функции scroll() для создания анимации, управляемой прокруткой:

  • НЕОБЯЗАТЕЛЬНО. Укажите, какой скроллер вы хотите использовать, если это не ближайший предок. Для этого используйте scroll(root) или scroll(self).
    • Если root, nearest или self недостаточно, используйте именованную временную шкалу прокрутки.
  • НЕОБЯЗАТЕЛЬНО. Укажите ось, которую нужно отслеживать. Если таргетинг настроен не на ось block по умолчанию (например, при горизонтальной прокрутке), укажите, какую ось нужно отслеживать, с помощью scroll(block) или scroll(inline).

В этом конкретном случае:

  • Если в свойстве animation-range используются проценты, то анимируемый элемент НЕ ДОЛЖЕН быть position: static или position: relative.
    • Это связано с тем, что такие элементы считаются "внутрипоточными". Если бы они уменьшались при прокрутке, то уменьшалось бы и общее расстояние прокрутки, что повлияло бы на вычисленное значение, например 10%.

Поддержка браузеров и резервные стратегии

Поэтому обычно требуется резервная стратегия.

Для браузеров, которые не поддерживают анимацию на основе прокрутки, можно использовать резервный вариант, чтобы воссоздать визуальные эффекты. Резервные варианты обычно создаются с помощью прослушивателя прокрутки (для эффектов ScrollTimeline) или IntersectionObserver API (для эффектов ViewTimeline).

В браузерах со встроенной поддержкой анимации на основе прокрутки ВСЕГДА используйте нативную реализацию CSS, так как она более эффективна.

Обратите внимание, что не все эффекты можно воссоздать с помощью резервных объявлений.

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

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: scroll()) and (animation-range: 0% 100%)')) {
  const header = document.querySelector('header');

  const initialHeight = 200;
  const finalHeight = 50;
  const scrollDistance = 150;

  window.addEventListener('scroll', () => {
    const scrollY = window.scrollY;
    const scrollPercent = Math.min(1, scrollY / scrollDistance);
    const newHeight = initialHeight - (initialHeight - finalHeight) * scrollPercent;

    header.style.height = `${newHeight}px`;
  });
}