Skip to content
PПромтбук
RUEN
02Motion

Performance-бюджет для motion

60 FPS на mid-range mobile, INP < 200ms, optimization checklist: transform-only, will-change по уму, GPU layers, composite.

Установи и проверь performance-бюджет для motion в проекте. Целевое устройство: {{target_device}}. Цель — анимации, которые не отнимают у пользователя scroll, ввод и батарею.

1. Бюджет (hard limits)

МетрикаБюджетГде замерять
FPS во время анимации≥ 60 FPS на mid-range mobileDevTools Performance → FPS meter
INP (взаимодействие во время motion)< 200msDevTools Performance Insights, real user monitoring
Длительность главной анимации≤ 400ms (UI) / ≤ 1000ms (orchestration)таймлайн
GPU memory от will-change< 30 MB суммарноDevTools → Performance → GPU
Long tasks от motion JS0 задач > 50msDevTools → Performance
CLS от анимации0.0Lighthouse, web-vitals lib

Если хоть один пункт красный — анимация не релизится.

2. Только composite-friendly свойства

GOOD (compositor only, GPU)
  transform: translate / scale / rotate
  opacity
  filter (с осторожностью — на старых GPU дорогой)

BAD (layout / paint, CPU)
  width / height / top / left / right / bottom
  margin / padding
  border-width
  font-size
  background-color (paint)
  box-shadow (paint, особенно крупный blur)
/* плохо */
.menu { transition: left 300ms; left: 0; }
.menu.closed { left: -300px; }

/* хорошо */
.menu { transition: transform 300ms; transform: translateX(0); }
.menu.closed { transform: translateX(-100%); }

3. will-change — правильно

/* НЕПРАВИЛЬНО — всегда включено */
.card { will-change: transform; } /* съедает GPU memory вечно */

/* ПРАВИЛЬНО — только во время анимации */
.card { transition: transform 250ms; }
.card:hover { will-change: transform; transform: translateY(-4px); }
.card { /* JS убирает will-change по transitionend */ }
function animate(el: HTMLElement) {
  el.style.willChange = "transform, opacity";
  el.classList.add("active");
  el.addEventListener("transitionend", () => {
    el.style.willChange = "auto"; // отпускаем GPU
  }, { once: true });
}

Правило: will-change — обещание браузеру. Когда оно вечное, браузер держит layer навсегда → GPU memory leak → тротлинг на mobile.

4. GPU layers — где они нужны

Принудительно создавай compositor layer только для элементов, которые реально анимируются:

.animated-card {
  transform: translateZ(0); /* или translate3d(0,0,0) — promotes to GPU layer */
}
  • Слишком много layers (100+) → GPU memory pressure → throttle.
  • Слишком мало → каждая анимация триггерит paint/composite → jank.
  • Sweet spot: layer на каждом независимо анимирующемся элементе, не больше.

DevTools → More tools → Layers — посмотри, сколько у тебя их сейчас.

5. Optimization checklist

  • Все анимации используют только transform и opacity (нет height/top/box-shadow)
  • Нет вечного will-change — только pre-animation, снимается after
  • requestAnimationFrame для всех scroll/resize handlers
  • { passive: true } на scroll/touch listeners
  • Нет getBoundingClientRect / offsetWidth внутри rAF (force reflow)
  • Изображения в анимации имеют фиксированные width/height (нет CLS)
  • Анимация выключена за пределами viewport (CSS animation-play-state или IO)
  • prefers-reduced-motion обработан везде
  • Lottie/Rive — препроцессированы, ≤ 50 KB на анимацию
  • Heavy SVG-фильтры (feGaussianBlur, feTurbulence) — НЕ на mobile
  • Backdrop-filter — НЕ более чем на 1 элементе на экране
  • Видео в hero — poster, preload="metadata", autoplay только при visibility

6. Как мерить

FPS

  1. DevTools → Rendering → Frame Rendering Stats
  2. CPU throttling: 4x slowdown
  3. Network: Fast 3G
  4. Сними 5-секундный record во время анимации
  5. Зелёный график (стабильно 60) или красный (drops)?

INP

  1. DevTools → Performance Insights → live mode
  2. Кликни во время анимации
  3. Найди event в треке → Interaction
  4. Длительность ≤ 200ms?

Real device

  • WebPageTest на реальном Moto G Power / iPhone SE.
  • Chrome DevTools Remote Debugging для Android.
  • Safari Web Inspector для iOS.

7. Когда снимать с релиза

  • FPS < 45 на target device → откладывай или упрощай (короче, меньше элементов).
  • INP > 300ms → анимация блокирует interaction. Уменьши scope или убери JS-handler.
  • Long task > 100ms от motion library — рефактор (lazy load Framer Motion, debounce, virtualize).
  • GPU memory > 50 MB на странице — мобильный Safari прибьёт вкладку.

8. Формат вывода

## Motion performance audit
### Замеры (до)
| Метрика | Значение | Бюджет | Статус |
|---|---|---|---|
| FPS hero anim | 42 | 60 | FAIL |
| INP during scroll | 280ms | 200 | FAIL |
| Layer count | 187 | <50 | FAIL |

### Найденные проблемы
1. `.hero-bg { animation: gradient-shift }` — paint каждый кадр
2. `.card { will-change: transform }` 96 штук вечно — 28MB GPU
3. ...

### Фиксы
[патчи]

### Замеры (после)
[таблица]

Анти-паттерны

  • will-change: transform на 50+ карточках вечно — 30+ MB GPU съедено, fps дропает даже без анимации.
  • ❌ Анимация height от 0 до auto — layout reflow каждый кадр, всегда jank.
  • box-shadow transition с большим blur — paint-bound, 30 FPS на mobile.
  • backdrop-filter: blur на скроллирующейся карточке — full-screen repaint каждый кадр.
  • ❌ Замер FPS без CPU throttling — на M2 Pro всё летает, на Moto G тормозит.
  • ❌ Animating SVG filter — самая дорогая операция, на mobile почти всегда < 30 FPS.
  • ❌ Lottie 500 KB+ JSON — парсинг блокирует main thread на 500-1000ms.
  • ❌ Hero видео без preload="metadata" и без poster — гонка с LCP, CLS up.
  • ❌ Не выключать infinite-анимации вне viewport — батарея и CPU горят за зря.
  • ❌ Релизить motion без замера на реальном устройстве — DevTools throttling не = реальный mobile GPU.
К подразделу «Motion»
Похожие промты