Не все CSS-свойства анимировать одинаково легко для браузера. transform и opacity обрабатываются на отдельном этапе рендеринга (composite) и не требуют пересчёта геометрии страницы - GPU справляется с ними на отдельном потоке. А width, height, top, left, margin заставляют браузер заново пересчитывать расположение элементов на КАЖДОМ кадре анимации - именно поэтому такие анимации могут дёргаться там, где transform остаётся идеально плавным.
В этом уроке
Попробуй прямо сейчас: это учебная демонстрация, а не рабочая кнопка "наверх" - клик по ней ничего не делает, JavaScript и прокрутка страницы здесь не задействованы. Наведение курсора поднимает кнопку через transform: translateY() - визуально она "подпрыгивает" вверх. Замени transform: translateY(-6px) на bottom: 30px (уберёт transform из hover-правила и добавит смещение через bottom) - результат на глаз будет очень похож, но это разные по стоимости операции для браузера: bottom меняет геометрию (Layout), а transform - нет. Разница не видна на одной кнопке, но на сложной странице с десятками элементов она становится заметной.
Почему одни свойства анимировать дороже других: конвейер рендеринга
Браузер рисует каждый кадр страницы через 4 последовательных этапа: Style (пересчёт стилей) → Layout (пересчёт геометрии и позиций всех элементов) → Paint (закраска пикселей) → Composite (сборка готового кадра из слоёв). Если анимация меняет свойство, которое влияет на геометрию - width, height, top, left, margin и подобные - браузер обязан заново пройти ВСЕ четыре этапа на каждом кадре. А transform и opacity влияют только на последний этап, Composite - Layout и Paint пропускаются целиком, и львиную долю работы делает GPU на отдельном потоке, независимом от основного потока браузера.
.box {
position: absolute;
transition: left 0.3s;
}
.box.moved {
left: 200px;
} - left меняет геометрию, каждый кадр анимации запускает полный Layout заново.
.box {
transition: transform 0.3s;
}
.box.moved {
transform: translateX(200px);
} - transform не трогает геометрию вообще, Layout и Paint пропускаются, работает только Composite.
Собственная проверка (Playwright + Chrome DevTools Protocol, реальные счётчики браузера Performance.getMetrics): 60 кадров анимации width дали LayoutCount +60 - ровно один полный пересчёт геометрии страницы на каждый кадр без исключения. Те же 60 кадров анимации transform дали LayoutCount +0 - ни единого дополнительного пересчёта геометрии, хотя пересчёт стилей (RecalcStyleCount) вырос одинаково в обоих случаях. Цифры времени подтверждают то же самое: +3.22мс суммарного времени на Layout за 60 кадров width против +0.00мс за 60 кадров transform.
Бюджет на один кадр при 60fps - ровно 16.7мс (1000мс / 60 кадров). Если браузер не укладывается в этот бюджет на каждом кадре, анимация визуально дёргается. На одном простом элементе Layout почти ничего не стоит - но на странице с десятками анимируемых элементов и сложной вёрсткой лишние 60 полных пересчётов геометрии в секунду вполне способны съесть этот бюджет и превратить анимацию в дёрганую.
Что делать вместо width, top и left: практические замены
У большинства "дорогих" анимаций есть готовая замена через transform без изменения визуального результата. Смещение через top/left/margin заменяется на translate()/translateX()/translateY() (урок про перемещение уже разбирал этот синтаксис). Изменение размера через width/height заменяется на scale(). Оба варианта дают идентичный визуальный результат, но translate()/scale() не трогают Layout вообще.
.panel { left: 0; transition: left 0.3s; }
.panel.open { left: 300px; } - каждый кадр анимации запускает Layout.
.panel { transform: translateX(0); transition: transform 0.3s; }
.panel.open { transform: translateX(300px); } - визуально то же самое смещение на 300px, но Layout не запускается ни разу.
Важная оговорка: transform и opacity не бесплатны абсолютно - Composite и пересчёт стилей всё равно происходят на каждом кадре, просто анимировать их заметно легче, чем Layout и Paint. И не все визуальные эффекты одинаково "чистые": filter и backdrop-filter (уже пройдены раньше) - не composite-only свойства, они требуют перекраски затронутых пикселей на каждом кадре, а значит для длительных или бесконечных анимаций обходятся дороже, чем чистый transform/opacity. Отдельное свойство will-change, которое заранее подсказывает браузеру подготовить элемент к анимации, разбирается в следующем уроке.
Ещё один практический плюс composite-only анимаций: они выполняются на отдельном потоке компоузитора, независимом от основного потока браузера, где работает JavaScript, Layout и Paint. Поэтому анимация на transform продолжает идти плавно, даже если основной поток в этот момент занят тяжёлым JS-кодом - а анимация на width или left в той же ситуации неизбежно затормозится вместе с основным потоком.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Анимировать width, height, top, left или margin вместо transform | Каждый кадр анимации запускает полный пересчёт геометрии страницы (Layout) - на сложной странице анимация может дёргаться | Заменять на transform: translate()/scale() - визуально то же самое, но без Layout |
| Считать transform полностью бесплатным для браузера | На самом деле Composite и пересчёт стилей всё равно происходят каждый кадр - просто это легче Layout и Paint | Понимать transform как "легко", а не "бесплатно" - экономия относительная, а не абсолютная |
| Использовать filter/backdrop-filter для длительных или бесконечных анимаций без проверки производительности | Это не composite-only свойства - требуют перекраски (Paint) затронутых пикселей на каждом кадре, дороже transform | Проверять реальную производительность в DevTools перед тем, как оставлять фильтр в бесконечной анимации |
| Делать выводы о производительности "на глаз", без проверки в инструментах разработчика | Визуально width- и transform-анимация могут выглядеть одинаково плавно на простой странице - разница видна только под нагрузкой | Проверять реальные счётчики Layout/Paint в панели Performance браузера, а не полагаться на впечатление |
Шпаргалка
| Запись | Что означает |
|---|---|
| Style → Layout → Paint → Composite | 4 последовательных этапа рендеринга каждого кадра |
| transform, opacity | Composite-only - Layout и Paint пропускаются, работает GPU на отдельном потоке |
| width, height, top, left, margin | Запускают Layout на каждом кадре анимации - дороже всего |
| filter, backdrop-filter | Требуют Paint на каждом кадре - дороже transform/opacity, легче Layout-свойств |
| 16.7мс (1000мс / 60fps) | Бюджет времени на один кадр для плавных 60 кадров в секунду |
Коротко: отвечаем на вопросы
Почему transform и opacity анимировать легче, чем width или top?
Браузер рисует каждый кадр через 4 этапа: Style, Layout, Paint, Composite. width, height, top, left и margin меняют геометрию страницы, поэтому запускают ВСЕ четыре этапа заново на каждом кадре. transform и opacity влияют только на последний этап (Composite) - Layout и Paint пропускаются целиком. Собственная проверка через реальные счётчики браузера (Chrome DevTools Protocol) показала: 60 кадров анимации width дали 60 полных пересчётов геометрии (LayoutCount +60), а те же 60 кадров анимации transform - ни одного (LayoutCount +0).
Правда ли, что анимация на transform совсем не нагружает браузер?
Нет, это преувеличение. Composite и пересчёт стилей всё равно происходят на каждом кадре анимации transform - просто это заметно легче, чем Layout и Paint, которые запускает изменение width или top. Правильная формулировка - "легко", а не "бесплатно": экономия относительная, а не полное отсутствие затрат.
Как заменить анимацию left или top на transform без изменения визуального результата?
Смещение через top/left/margin заменяется на translate()/translateX()/translateY() с тем же числом пикселей - визуально движение выглядит идентично, но геометрия страницы не пересчитывается. Изменение размера через width/height аналогично заменяется на scale(). Оба варианта дают тот же результат на экране, но браузеру их заметно легче анимировать.
Продолжают ли анимации на transform работать плавно, если основной поток браузера занят тяжёлым JS?
Да. Composite-анимации на transform и opacity выполняются на отдельном потоке компоузитора, независимом от основного потока, где работает JavaScript, Layout и Paint. Поэтому такая анимация продолжает идти плавно, даже когда основной поток занят тяжёлыми вычислениями - а анимация на width или left в той же ситуации неизбежно затормозится вместе с ним.