Уменьшающийся при прокрутке заголовок – это распространенный шаблон пользовательского интерфейса, при котором фиксированный элемент заголовка в верхней части страницы плавно уменьшается в размере по мере того, как пользователь прокручивает страницу вниз. Этот эффект часто используется, чтобы максимально увеличить площадь экрана для основного контента, сохраняя при этом доступность основных элементов навигации или брендинга. С помощью анимации на основе прокрутки CSS этот эффект можно реализовать декларативным и эффективным способом, связав анимацию с положением прокрутки документа.
Рекомендации по внедрению
Вот как создать уменьшающийся заголовок при прокрутке:
Создайте фиксированный заголовок. Начните с элемента заголовка, который закреплен в верхней части страницы и имеет заданную высоту.
<header>HEADER</header>header { position: fixed; height: 200px; top: 0; left: 0; right: 0; }Определите анимацию уменьшения. Создайте CSS-анимацию, которая изменяет высоту заголовка.
@keyframes shrink { to { height: 50px; } }Примените анимацию и временную шкалу прокрутки. Прикрепите анимацию к заголовку и используйте функцию
scroll(), чтобы связать ее с положением прокрутки документа.header { animation: shrink auto linear both; animation-timeline: scroll(block root); }Задайте свойство
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`;
});
}