Scroll-анимации тесно связаны с частыми событиями прокрутки, которые могут вызываться десятки раз в секунду. Каждое такое событие потенциально запускает вычисления, перерисовку интерфейса и перерасчёт стилей. При неэффективной реализации это приводит к снижению FPS, «дёрганию» интерфейса и увеличенной нагрузке на процессор.
Основные проблемы:
scrolltop,
left, width)Библиотека Motion One решает значительную часть этих проблем за счёт оптимизации анимаций и использования современных API браузера.
requestAnimationFrameMotion One синхронизирует анимации с циклом рендеринга браузера через
requestAnimationFrame. Это позволяет:
Анимации, построенные на свойствах transform и
opacity, выполняются на уровне GPU. Motion One по умолчанию
ориентируется именно на эти свойства.
Оптимальные свойства:
transform: translate/scale/rotateopacityМенее эффективные:
width, heightmargin, paddingtop, leftMotion One предоставляет функцию scroll, которая
позволяет связывать анимации с положением прокрутки.
import { scroll, animate } from "motion"
scroll(
animate(".box", { transform: ["scale(0)", "scale(1)"] }),
)
В этом примере анимация масштабирования синхронизируется с прокруткой страницы.
Motion One вычисляет прогресс прокрутки (от 0 до 1) и использует его для интерполяции значений.
scroll(({ progress }) => {
animate(".box", {
transform: `translateY(${progress * 100}px)`
})
})
Однако такой подход может быть неэффективен, если анимация создаётся заново на каждом кадре. Более производительный способ — использовать заранее объявленные анимации.
Лучший вариант — передавать готовую анимацию:
const animation = animate(".box", {
transform: ["translateY(0px)", "translateY(200px)"]
})
scroll(animation)
Преимущества:
Motion One позволяет ограничивать диапазон, в котором работает scroll-анимация:
scroll(
animate(".box", { opacity: [0, 1] }),
{ offset: ["start end", "end start"] }
)
Это снижает нагрузку:
Для оптимизации Motion One использует механизмы, аналогичные
Intersection Observer, чтобы:
Преимущества:
Layout thrashing возникает при чередовании чтения и записи layout-свойств:
const height = element.offsetHeight
element.style.height = height + 10 + "px"
Motion One минимизирует такие операции, но при кастомной логике важно:
При ручной работе с прокруткой важно ограничивать частоту вызовов:
let ticking = false
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(() => {
// логика
ticking = false
})
ticking = true
}
})
Motion One уже реализует подобный механизм, поэтому прямое
использование scroll() предпочтительнее.
Несколько scroll-анимаций могут работать одновременно:
scroll(
animate(".box", { opacity: [0, 1] })
)
scroll(
animate(".box", { transform: ["scale(0.5)", "scale(1)"] })
)
Для производительности важно:
При анимации списков или сеток:
Проблемы:
Решения:
will-change.box {
will-change: transform, opacity;
}
Однако чрезмерное использование will-change увеличивает
потребление памяти.
Анимации запускаются только при появлении элемента:
scroll(
animate(".item", { opacity: [0, 1] }),
{ target: ".item" }
)
Это:
Инструменты:
На что обращать внимание:
| Подход | Производительность | Сложность |
|---|---|---|
| Чистый scroll listener | низкая | низкая |
| requestAnimationFrame | средняя | средняя |
| Motion One | высокая | низкая |
Motion One выигрывает за счёт:
transform и opacityПроизводительные scroll-анимации:
Непроизводительные:
Оптимальная структура:
Это позволяет масштабировать проект без деградации производительности.