Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чому transform кращий для анімацій, ніж top, left». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**CSS `transform`** пропускає layout і paint на кожному кадрі анімації; `top`/`left` запускає обидва з початку. ```css /* Повільно: повний перерахунок layout кожного кадру */ @keyframes bad { to { top: 50px; } } /* Швидко: тільки GPU composite, layout не задіяний */ @keyframes good { to { transform: translateY(50px); } } ``` **Ключове:** `transform` переносить елемент на GPU-шар; `top`/`left` навантажує CPU перерахунком layout на кожному кадрі.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**CSS `transform`** переміщує елемент, застосовуючи матрицю до вже намальованого шару, тому браузер пропускає layout і paint на кожному кадрі анімації. `top` і `left` змінюють місце елемента в документі, що змушує браузер перераховувати все знову щокадру. ## Теорія ### TL;DR - `top`/`left` - як переставляти меблі: все навколо зсувається. `transform` - як посунути зображення проєктора: кімната стоїть на місці. - Браузер рендерить у три кроки: layout, paint, composite. `transform` торкається лише останнього. `top`/`left` запускає все з початку. - Правило вибору: будь-який візуальний рух (hover, scroll-тригер, slide-in) - `transform`. `top`/`left` лишаються для статичного позиціювання, яке не анімується. - GPU обробляє `transform` асинхронно, поза головним потоком. `top`/`left` навантажує CPU на кожні 16 мс кадру. ### Короткий приклад ```css /* Погано: браузер перераховує layout щокадру */ @keyframes bad { to { top: 50px; left: 50px; } /* layout + paint кожен кадр */ } /* Добре: браузер пропускає layout і paint повністю */ @keyframes good { to { transform: translate(50px, 50px); } /* тільки composite */ } .box-bad { position: relative; animation: bad 1s infinite; } .box-good { animation: good 1s infinite; } ``` На складній сторінці `bad` просідає до 10-20fps під час скролу. `good` тримає 60fps, бо GPU обробляє анімацію незалежно від головного потоку. ### Головна різниця Браузер рендерить у три кроки: **layout** (розрахунок позицій), **paint** (малювання пікселів), **composite** (складання шарів). Анімація `transform` переносить елемент на окремий GPU-шар і оновлює лише composite-крок кожного кадру. `top` і `left` скасовують геометрію елемента, браузер іде вверх по дереву layout, перераховує позиції від кореня документа, потім ставить у чергу paint для всіх зачеплених зон. Один кадр коштує кілька мілісекунд, а при 60fps бюджет на кадр - 16 мс загалом. ### Коли що використовувати - Візуальний рух (hover-підняття, slide-in, паралакс): `transform: translate` - Масштабування без зміщення сусідів: `transform: scale` - Обертання або flip-анімація: `transform: rotate` - Статичне позиціювання, точний контроль потоку: `top`/`left` підходить - Mobile або критичний 60fps: завжди `transform` ### Таблиця порівняння | Аспект | `transform` | `top` / `left` | |---|---|---| | Кроки рендерингу | Тільки composite (GPU) | Layout + Paint + Composite | | Типовий fps під навантаженням | 60fps | 10-20fps | | Впливає на потік документа | Ні | Так, зсуває сусідів | | GPU-прискорення | Автоматичне виділення шару | Ні (CPU) | | Продуктивність при скролі | Не змінюється | Може запускати reflow | | Коли використовувати | Всі анімації, переходи | Тільки статичне позиціювання | ### Як браузер обробляє це Коли анімується `transform`, рушій рендерингу Chrome (Blink) переносить елемент на `cc::Layer` і передає його GPU-бекенду (Skia). Наступні кадри оновлюють лише composite-матрицю, без дерева layout. `top`/`left` скасовує геометрію `RenderBox`: браузер обходить дерево layout, позначає вузли як застарілі, перераховує позиції, потім ставить у чергу paint для всіх зачеплених ділянок. На сторінці з сайдбаром, хедером і floating-картками - це багато вузлів щокадру. `will-change: transform` підказує браузеру виділити шар завчасно, уникаючи затримки на перший кадр. Але застосовувати його варто лише на елементах, які справді будуть анімуватися. `will-change: transform` одночасно на 100+ елементах може з'їсти 200 МБ GPU-пам'яті і покласти вкладку. ### Типові помилки **Анімація `width` або `height` для ефекту росту:** ```css /* Запускає перерахунок layout кожного кадру */ @keyframes grow-bad { to { width: 200px; } } /* Правильно: scale не торкається layout */ @keyframes grow-good { to { transform: scale(2); } } ``` **`position: relative` + `top` для slide-in:** ```css /* Зсуває всі наступні елементи під час анімації */ @keyframes slide-bad { from { top: -20px; } to { top: 0; } } /* Не торкається потоку документа взагалі */ @keyframes slide-good { from { transform: translateY(-20px); } to { transform: translateY(0); } } ``` **Надмірне використання `will-change` на статичних списках:** ```css /* Витікає GPU-пам'ять на 50+ елементах */ .list-item { will-change: transform; } /* Краще: виділяй шар тільки при hover */ .list-item:hover { will-change: transform; } ``` **Поєднання `transform` з `margin` для зміщення:** ```css /* margin: 0 auto центрує в потоці; translate-зміщення конфліктує з ним */ .bad { margin: 0 auto; transform: translateX(10px); } /* Правильно: ланцюг transform для комбінованого зміщення */ .good { left: 50%; transform: translateX(-50%) translateX(10px); } ``` ### Де зустрічається - React Transition Group використовує `transform: translate3d` для анімацій переходів між сторінками - Framer Motion за замовчуванням рухає `motion.div` через `transform` - GSAP компілює `x: 100` у `translateX(100px)` всередині - Swiper.js запускає свайпи каруселі через `translate3d` - Tailwind `animate-pulse` використовує `scale` і `opacity`, а не `width`/`height` ### Питання на співбесіді **Q:** Чому `transform` не впливає на layout, навіть якщо елемент рухається візуально? **A:** `transform` застосовує матрицю до намальованого шару після того, як layout завершено. Потік документа бачить оригінальний нетрансформований блок. Сусідні елементи не знають, що щось змінилось візуально. **Q:** Коли браузер виділяє елемент в окремий composite-шар? **A:** При анімації `transform`, `opacity` або 3D-властивостей, а також при `will-change: transform`. Перевірити поточний стан шарів можна у вкладці Layers у Chrome DevTools. **Q:** `transform` дає таку ж піксельну точність, як `top`/`left`? **A:** Так. Дробові значення типу `translateX(0.5px)` рендеряться через GPU subpixel blending, що навіть плавніше. Якщо бачиш розмиті краї - додай `backface-visibility: hidden` для чистого шару. **Q:** Як відлагодити зависання анімацій у продакшені? **A:** Відкрий вкладку Performance у DevTools, запиши під час анімації. Шукай "Recalculate Style" і "Layout" у flame chart. Ці піки вказують на layout thrash. Чиста `transform`-анімація показує лише "Composite Layers". **Q:** Є особливості у Safari? **A:** На iOS 12 і раніше потрібен був `transform: translateZ(0)` для примусового виділення шару. З iOS 13 звичайний `transform` працює без хаку. Все одно краще перевіряти на реальних пристроях, а не тільки в симуляторі. ## Приклади ### Підняття картки при hover Поширений патерн у продуктових інтерфейсах: картка злегка піднімається при наведенні. Неправильний варіант - `top: -8px` при hover. Це зсуває картку в потоці, сторінка «стрибає», запускається layout. Правильний варіант: ```css .card { transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .card:hover { transform: translateY(-8px) scale(1.02); } ``` Сусіди не зсуваються. Перерахунку layout немає. GPU обробляє весь перехід, і ефект однаково плавно працює як на одній картці, так і в гриді з сотнею. ### Slide-in нотифікація ```jsx // React-компонент: нотифікація виїжджає справа function Notification({ visible, message }) { return ( <div style={{ transform: visible ? 'translateX(0)' : 'translateX(110%)', transition: 'transform 0.3s ease-out', position: 'fixed', right: 16, bottom: 16, }} > {message} </div> ); } ``` `position: fixed` визначає де елемент знаходиться у viewport. `transform` відповідає за анімацію. Це два різних обов'язки, і саме їх розділення робить патерн плавним. Анімувати `right` замість `transform` - помилка, яку я бачив у кількох продакшн-кодбазах. Локально виглядає нормально, а на бюджетному Android дає помітний джанк. Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.