Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Властивість CSS will-change». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`will-change`** - це CSS-підказка, яка повідомляє браузер про те, які властивості елемента зміняться, щоб він міг виділити GPU compositor layer ще до початку анімації. ```css .card:hover { will-change: transform; transform: scale(1.05); } ``` **Головне правило:** додавай безпосередньо перед анімацією (через `:hover` або JS), прибирай через `will-change: auto` після. Якщо застосувати до всього і назавжди, браузер створить сотні GPU-шарів і продуктивність впаде, а не зросте.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`will-change`** повідомляє браузер про те, які CSS-властивості елемента зміняться, щоб він міг виділити GPU compositor layer ще до початку анімації. ## Теорія ### TL;DR - Як замовлення страви наперед: кухня готує до твого приходу, а не після - Без нього: браузер перемальовує через CPU на кожному кадрі, з'їдаючи 16ms бюджету - З ним: GPU компонує `transform` і `opacity` поза головним потоком, затримка падає нижче 5ms - Додавай за 1-2 кадри до початку анімації через `:hover` або JS; прибирай через `will-change: auto` після - Працює тільки для compositor-fast властивостей: `transform`, `opacity`, `filter`, `scroll-position` ### Швидкий приклад ```css .card { transition: transform 0.3s ease; /* Без will-change: CPU перемальовує кожен кадр */ } .card:hover { will-change: transform; /* Браузер створює GPU-шар при hover */ transform: scale(1.05); /* GPU компонує — головний потік вільний */ } /* DevTools > Layers: новий шар з'являється при hover і зникає після */ ``` Коли спрацьовує `.card:hover`, браузер бачить `will-change` ще до початку transition. Цього достатньо, щоб виділити compositor layer. Анімація масштабу проходить повністю поза головним потоком. ### Як насправді працює layer promotion За замовчуванням браузер малює все на головному потоці. Кожна зміна `transform` або `opacity` без compositor layer запускає repaint: CPU перераховує пікселі, відправляє на GPU, де відбувається blending. При 60fps є всього 16ms на кадр. Завантажений головний потік легко з'їдає цей бюджет. `will-change` дозволяє браузеру обійти цей потік. В движку Blink Chrome, отримавши `will-change: transform`, виділяє `cc::Layer`. GPU-процес растеризує елемент в тайли один раз і компонує кожен кадр, не торкаючись layout і paint. Витрати на кадр падають з 50ms+ до менш ніж 5ms для transform і opacity. Але кожен шар займає GPU-пам'ять, зазвичай кілька MB залежно від розміру елемента. 50 шарів на мобільному це вже 200MB+. Chrome тримає soft budget приблизно на 20 активних шарів на stacking context і автоматично прибирає зайві через 250ms простою (Chrome 110+). Safari прибирає швидше, приблизно за 60ms. ### Коли використовувати - **Hover-анімації:** встановлюй `will-change` на `:hover`, щоб GPU-шар вже існував коли запускається transition - **Анімації через JS:** `element.style.willChange = 'transform'` за один [requestAnimationFrame](/questions/request-animation-frame) до старту анімації - **Модалки:** встановлюй при відкритті, прибирай через `will-change: auto` в обробнику `transitionend` - **Scroll-driven ефекти:** тільки для елементів у viewport, не для всього списку одразу ### Різниця з `transform: translateZ(0)` `transform: translateZ(0)` відразу і назавжди створює GPU-шар. Цей підхід існував до появи `will-change` і був поширеним хаком до 2015 року. `will-change` виділяє шар ліниво і прибирається через `will-change: auto`. Для статичних елементів, які не анімуються, `translateZ(0)` може навпаки зашкодити, тримаючи шар вічно. Для керованих анімацій `will-change` краще, бо ти сам контролюєш lifecycle шару. ### Типові помилки **Додавати до всього при завантаженні сторінки** ```css /* 100 карток = 100+ GPU шарів = 500MB RAM, лагаючий скрол */ .card { will-change: transform; } ``` Я одного разу аудитив продуктову сітку саме з такою проблемою. Кожна `.product-card` мала `will-change: transform` в глобальному стилі, DevTools Layers показував 80+ активних compositor шарів, а frame rate при скролі застряг на 30fps. Прибрав глобальне правило і додав `will-change` на `mouseenter` — скрол повернувся до стабільних 60fps. ```javascript el.addEventListener('mouseenter', () => { el.style.willChange = 'transform'; }); el.addEventListener('transitionend', () => { el.style.willChange = 'auto'; }); ``` **Не прибирати після анімації** ```css /* Шар залишається виділеним навіть після завершення анімації */ .animate { will-change: transform; animation: spin 2s; } ``` На iOS Safari це дає 30% падіння продуктивності при наступному скролі (WebKit bug 148037). Завжди скидай: `el.style.willChange = 'auto'` в обробнику `animationend`. **Використовувати для non-composited властивостей** ```css /* will-change: width не дає жодної GPU-оптимізації */ .bad { will-change: width; transition: width 1s; } ``` Compositor promotion отримують тільки `transform`, `opacity`, `filter` і `scroll-position`. Властивості `width`, `height`, `color`, `margin` не переміщуються на GPU. Браузер просто ігнорує підказку для них. Якщо потрібна анімація ширини, імітуй її через `transform: scaleX()`. **Встановлювати для великих списків без обмежень** ```css /* 50 елементів * will-change = пік пам'яті на середньому Android */ .scroll-list > * { will-change: transform; } ``` Chrome 120+ автоматично прибере зайві шари через 250ms простою, але сам пік виділення пам'яті вже викличе jank. Застосовуй `will-change` тільки до елементів поблизу viewport через JS. ### Де це зустрічається - GSAP 3.12+: `gsap.set(el, { willChange: 'transform' })` перед будь-яким tween - React Spring v9+: патерн з `useWillChange` хуком для parallax-модалок (react-spring issue #782) - Framer Motion: автоматично керує `will-change` для spring анімацій - Ionic 7: `will-change` на modal overlays для плавності в iOS Safari PWA - Chrome DevTools: у DevTools Layers видно `will-change` в анімації Gmail compose-вікна ### Follow-up питання **Q:** Для яких CSS-властивостей `will-change` реально дає GPU compositor promotion? **A:** `transform`, `opacity`, `filter` і `scroll-position`. Властивості `width`, `height`, `top`, `left`, `color` promotion не отримують. Для них браузер просто ігнорує підказку. **Q:** Яка різниця між `will-change: transform` і `transform: translateZ(0)`? **A:** `translateZ(0)` відразу і назавжди створює GPU-шар. `will-change` виділяє ліниво і прибирається через `will-change: auto`. Для анімацій, якими ти керуєш, `will-change` кращий, бо lifecycle шару в твоїх руках. **Q:** Скільки `will-change` шарів Chrome тримає до початку деградації? **A:** Приблизно 20 активних шарів на stacking context. Chrome 110+ автоматично прибирає зайві через 250ms простою. Safari робить це швидше, за 60ms. Тиск на пам'ять можна перевірити через `performance.measureUserAgentSpecificMemory()`. **Q:** Чому `will-change: auto` не відразу звільняє GPU-шар? **A:** Браузер батчує демоції з мінімальною затримкою близько 128ms, щоб не викликати thrashing. Для перевірки дивись на Layers panel у DevTools після встановлення `will-change: auto`. **Q:** У тебе список зі 100 картками, у всіх `will-change: transform` і hover-анімації. Мобільні користувачі скаржаться на лаги скролу. Як виправити без `IntersectionObserver`? **A:** Віртуалізуй список через `react-window`, щоб у DOM було тільки 6-10 вузлів. Застосовуй `will-change` тільки до видимих елементів через `ResizeObserver` з throttle. Це скорочує активні шари з 100 до 3-5. Другий варіант: встановлювати `will-change` в `mouseenter` і прибирати в `transitionend`, щоб шар існував максимум 300ms на картку. ## Приклади ### Базовий: hover-картка з GPU compositing ```css .card { width: 200px; padding: 20px; background: #f0f0f0; transition: transform 0.3s ease, box-shadow 0.3s ease; } /* will-change встановлюється ДО початку transition */ .card:hover { will-change: transform; transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); } ``` Браузер виділяє GPU-шар у момент спрацювання `:hover`, ще до початку [CSS transition](/questions/css-transitions). Анімація `translateY` проходить повністю на compositor. Відкрий DevTools Layers: при hover з'явиться один зайвий шар, при відведенні миші зникне. ### Середній: модалка з lifecycle управлінням через JS ```javascript const modal = document.querySelector('.modal'); function openModal() { // Виділяємо GPU-шар за один кадр до початку анімації modal.style.willChange = 'transform, opacity'; requestAnimationFrame(() => { modal.classList.add('modal--visible'); }); } function closeModal() { modal.addEventListener('transitionend', () => { // Повертаємо шар після завершення анімації modal.style.willChange = 'auto'; }, { once: true }); modal.classList.remove('modal--visible'); } ``` ```css .modal { opacity: 0; transform: scale(0.92); transition: transform 0.25s ease, opacity 0.25s ease; } .modal--visible { opacity: 1; transform: scale(1); } ``` Встановлення `will-change` перед `requestAnimationFrame` дає браузеру один повний кадр на виділення шару. Опція `{ once: true }` прибирає слухач автоматично і уникає накопичення обробників при повторних відкриттях. ### Просунутий: viewport-scoped will-change для довгих списків ```javascript function manageWillChange(container) { const items = Array.from(container.querySelectorAll('.item')); function update() { items.forEach(item => { const { top, bottom } = item.getBoundingClientRect(); const inViewport = bottom > 0 && top < window.innerHeight; // Promote елементи поблизу viewport, demote решту item.style.willChange = inViewport ? 'transform, opacity' : 'auto'; }); } let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { update(); ticking = false; }); ticking = true; } }); update(); // Викликаємо одразу при монтуванні } ``` Незалежно від довжини списку, в будь-який момент скролу активних шарів буде не більше 5-8. `requestAnimationFrame` обмежує частоту оновлень до 60 разів на секунду. Chrome 120+ і сам би прибрав позаекранні шари через 250ms, але явне керування дає однакову поведінку в Safari і Firefox.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.