Эффект закрепления элемента при прокрутке страницы чаще всего
реализуется через position: sticky или
position: fixed. Однако статичное закрепление ограничивает
контроль над переходами между состояниями. Анимационный подход позволяет
управлять не только фактом «прилипания», но и динамикой перехода:
ускорением, инерцией, смещением, затуханием и изменением геометрии.
В основе анимационного sticky-поведения лежит идея двух состояний:
Переход между состояниями становится анимацией, а не мгновенным переключением CSS.
Velocity.js предоставляет инструментальную базу для построения таких переходов через высокопроизводительные анимации DOM-элементов.
Sticky-логика через JavaScript обычно строится вокруг трёх компонентов:
Ключевое отличие от CSS position: sticky заключается в
том, что состояние не просто переключается, а интерполируется.
Основная модель:
offsetTopСобытие scroll генерируется с высокой частотой, что
делает прямую обработку дорогостоящей. Для управления нагрузкой
применяется:
requestAnimationFrameТиповая схема:
let ticking = false;
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(() => {
handleScroll();
ticking = false;
});
ticking = true;
}
});
Функция handleScroll() становится точкой принятия
решения о запуске анимации.
Для вычисления состояния используется сравнение текущего положения прокрутки с координатой элемента:
const element = document.querySelector(".sticky");
const start = element.offsetTop;
function handleScroll() {
const scrollTop = window.scrollY;
if (scrollTop >= start) {
pinElement();
} else {
unpinElement();
}
}
В более сложных сценариях вводятся дополнительные параметры:
Это позволяет ограничивать «залипание» в пределах контейнера.
Переход между состояниями становится анимацией трансформации. Вместо
мгновенного изменения position применяется комбинация:
transform: translateYopacityscale (опционально)box-shadow для визуального отделенияПример закрепления:
function pinElement() {
Velocity(element, {
translateY: 0,
scale: 1,
opacity: 1
}, {
duration: 300,
easing: "easeOutCubic"
});
element.classList.add("is-fixed");
}
Параллельно CSS может управлять position: fixed, но
анимация делает переход визуально плавным.
Обратный переход требует сохранения ощущения непрерывности движения:
function unpinElement() {
Velocity(element, {
translateY: 20,
opacity: 0.95
}, {
duration: 200,
easing: "easeInCubic"
});
element.classList.remove("is-fixed");
}
Здесь важно не обнулять трансформации резко, иначе возникает визуальный «скачок».
Классическая ошибка sticky-анимаций — попытка анимировать
top или position. Это приводит к:
Оптимальная стратегия:
position меняется дискретноtransform.sticky {
will-change: transform;
}
will-change сообщает браузеру о предстоящих изменениях и
снижает стоимость перерисовки.
Часто sticky-элемент ограничивается родительским блоком. В анимационной модели добавляется нижняя граница:
const container = document.querySelector(".container");
const containerBottom = container.offsetTop + container.offsetHeight;
function handleScroll() {
const scrollTop = window.scrollY;
const elementHeight = element.offsetHeight;
if (scrollTop + elementHeight < containerBottom) {
if (scrollTop >= start) pinElement();
else unpinElement();
} else {
releaseFromContainer();
}
}
Функция releaseFromContainer() отвечает за «отлипание»
при достижении конца секции.
Velocity.js позволяет создавать эффект инерционного докручивания:
Velocity(element, {
translateY: [0, 30]
}, {
duration: 500,
easing: "spring"
});
Использование пружинной динамики создаёт ощущение физического движения, особенно при закреплении панели навигации.
Распространённый паттерн — уменьшение header при фиксации:
function pinHeader() {
Velocity(header, {
height: "60px",
translateY: 0
}, {
duration: 250
});
Velocity(logo, {
scale: 0.8
}, {
duration: 250
});
}
Комбинация нескольких анимаций позволяет визуально отделить состояние навигации от основного контента.
Sticky-анимации часто комбинируются с параллакс-эффектом. В этом случае элемент не фиксируется полностью, а смещается с разной скоростью относительно скролла.
function handleParallax() {
const scrollTop = window.scrollY;
const offset = scrollTop * 0.3;
Velocity(layer, {
translateY: -offset
}, {
duration: 0
});
}
Нулевая длительность превращает Velocity.js в высокопроизводительный tween-движок.
При наличии нескольких закрепляемых блоков возникает проблема очередности. Используется система слоёв:
const stickyItems = [header, sidebar, toolbar];
function updateSticky(scrollTop) {
stickyItems.forEach((item, index) => {
const threshold = item.offsetTop - index * 10;
if (scrollTop > threshold) {
Velocity(item, { translateY: 0 }, { duration: 200 });
}
});
}
Основные источники нестабильности:
Решение заключается в хранении флага состояния:
let isPinned = false;
function pinElement() {
if (isPinned) return;
isPinned = true;
Velocity(element, { translateY: 0 }, { duration: 300 });
}
Это предотвращает повторные вызовы и снижает нагрузку на движок анимации.
Sticky-анимации усложняются при изменении DOM:
В таких случаях пересчитываются координаты:
function recalc() {
start = element.getBoundingClientRect().top + window.scrollY;
}
Вызывается после событий:
resizeloadSticky-анимация может рассматриваться как временная шкала:
Velocity.js позволяет контролировать переходную фазу как непрерывную кривую, а не дискретное переключение.
Стабильная реализация sticky-анимации строится как конечный автомат:
normalenteringpinnedexitingКаждое состояние имеет собственные допустимые переходы, что исключает конфликт анимаций и обеспечивает предсказуемость поведения интерфейса.