CSS vs JS-анимации: скорость, удобство и выбор
Есть CSS и JS анимация. Какая между ними разница, что быстрее, что более удобно?
В чём разница CSS и JS
CSS-анимации (transition/animation) описывают «что должно меняться» (ключевые кадры, длительность, easing), а браузер решает «как именно» встроить это в рендеринг и какие оптимизации применить.
JS-анимации чаще означают ручное обновление значений во времени (например, через requestAnimationFrame(...)), то есть логика вычисления каждого кадра выполняется на main thread и конкурирует с прочими задачами JavaScript (обработчики событий, логика приложения, парсинг данных).
Web Animations API (WAAPI) находится между этими подходами: анимация задаётся из JavaScript, но исполняется как «движковая» анимация, поэтому при подходящих свойствах может оптимизироваться похожим образом, как CSS-анимации.
Почему одни анимации «быстрые»
Браузер формирует кадр через этапы: пересчёт стилей (recalculate style) → layout (геометрия) → paint (рисование пикселей) → composition/composite (сборка слоёв).
Если изменение свойства требует layout, то затем обычно потребуется и paint, а это быстро «съедает» бюджет кадра и приводит к рывкам, особенно на страницах со сложной версткой и большим количеством элементов.
Если изменение можно выполнить на этапе композиции (часто так бывает с transform и opacity, когда элемент вынесен в отдельный слой), то можно избежать layout и paint в каждом кадре и получить более стабильную плавность.
Схема (упрощённо)
JS/CSS меняет значения
|
Recalculate style
|
Layout (reflow) — пересчёт размеров/позиций
|
Paint — перерисовка пикселей
|
Composite — сборка слоёв и вывод на экран
Таблица стоимости свойств
| Категория изменения | Что обычно происходит | Примеры свойств | Что это значит для анимации |
|---|---|---|---|
| Layout + Paint + Composite | Требуется пересчитать геометрию и затем перерисовать | width, height, left, margin, font-size | Часто самая дорогая анимация, особенно на сложных страницах |
| Paint + Composite | Геометрия не меняется, но нужны новые пиксели | color, box-shadow (часто дорогой paint) | Может быть приемлемо для маленьких областей, но на больших — тяжело |
| Composite (иногда) | Можно обойтись сборкой слоёв без перерисовки | transform, opacity | Обычно лучший вариант для плавного движения/прозрачности |
will-change способно помочь браузеру подготовить оптимизации под будущую анимацию, но избыточное применение может тратить память, увеличивать количество слоёв и в итоге ухудшать производительность.Что удобнее в практике
CSS-анимации часто проще и короче по записи для типовых эффектов (появление, смещение, hover-переходы), их легче поддерживать, когда логики мало и важна декларативность: достаточно описать состояния и длительность.
JS/WAAPI удобнее, когда требуется управление временем и состоянием: поставить на паузу, продолжить, синхронизировать несколько эффектов, вычислять параметры на основе данных, связывать прогресс анимации с прокруткой или состоянием приложения.
С точки зрения производительности нельзя считать «CSS всегда быстрее»: если анимируется «дорогое» свойство, то main thread будет занят layout/paint независимо от того, задано это в CSS или из JS.
Код и диагностика
Ниже приведены примеры: «дешёвый» путь через transform/opacity и «дорогой» путь через свойства, затрагивающие геометрию, чтобы было видно, почему вариант 1 считается корректным.
// CSS: перемещение через transform (обычно дешевле)
.box {
will-change: transform, opacity;
animation: move 300ms ease-out forwards;
}
@keyframes move {
from { transform: translateX(0); opacity: 0.7; }
to { transform: translateX(200px); opacity: 1; }
}
// CSS: более дорогое изменение геометрии (часто требует layout + paint)
.box {
animation: grow 300ms ease-out forwards;
}
@keyframes grow {
from { width: 120px; }
to { width: 320px; }
}
// JS + requestAnimationFrame: ручной расчёт прогресса и обновление transform
const box = document.querySelector('.box');
const DURATION = 300;
const DIST = 200;
let start = null;
function tick(t) {
if (start === null) start = t;
const elapsed = t - start;
const p = Math.min(elapsed / DURATION, 1);
box.style.transform = `translateX(${DIST * p}px)`;
box.style.opacity = String(0.7 + 0.3 * p);
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
// WAAPI: анимация из JS, но исполняется «движком анимаций» браузера
const box = document.querySelector('.box');
box.animate(
[
{ transform: 'translateX(0px)', opacity: 0.7 },
{ transform: 'translateX(200px)', opacity: 1 }
],
{
duration: 300,
easing: 'ease-out',
fill: 'forwards'
}
);
Для диагностики производительности полезно записывать профили в Performance-инструментах браузера и смотреть, какие этапы доминируют по времени (layout, paint, composite) и насколько стабилен fps.
Если во время анимации видны частые и дорогие layout/paint, то даже «идеальный» таймер (CSS, WAAPI или requestAnimationFrame(...)) не исправит ситуацию, потому что основная стоимость уходит на перерасчёт и перерисовку.
Итого: корректным является вариант 1, потому что плавность определяется «ценой» анимируемых свойств и загрузкой main thread; варианты 2–4 неверны, так как CSS не гарантирует одинаковую скорость для любых свойств, JS не является «всегда быстрее», а удобство зависит от сценария (типовая анимация или логика и управление).