will-change — подсказка браузеру

will-change — CSS-свойство, сообщающее браузеру, какие свойства элемента изменятся в ближайшем будущем, позволяя браузеру заранее оптимизировать: создать compositor layer для GPU-ускорения анимаций трансформаций и прозрачности.

Зачем нужно

Без will-change браузер анимирует transform/opacity на CPU в main thread, что вызывает jank (пропуски кадров) при сложных страницах. С will-change: transform браузер переносит элемент на GPU compositor thread — анимация идёт независимо от main thread.

Где используется

  • CSS-анимации с transform и opacity (slidein, fadeout, модальные окна)
  • Scroll-triggered анимации
  • Параллакс эффекты
  • Drag-and-drop элементы

Основной контент

Базовое использование

/* Анимированный модальный диалог */
.modal {
  will-change: transform, opacity;
  /* Браузер создаёт compositor layer ЗАРАНЕЕ */
  /* Анимация идёт плавно на 60fps */
}

.modal.entering {
  animation: slideIn 0.3s ease-out;
}
.modal.leaving {
  animation: slideOut 0.2s ease-in;
}

@keyframes slideIn {
  from { transform: translateY(-20px); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

Правильный паттерн: динамическое добавление

// Добавляем will-change ПЕРЕД анимацией, убираем ПОСЛЕ
const button = document.querySelector('.animate-btn');
const card = document.querySelector('.card');

button.addEventListener('click', () => {
  // Шаг 1: Подготовка layer перед анимацией
  card.style.willChange = 'transform';

  // Шаг 2: Запуск анимации
  card.classList.add('animating');

  // Шаг 3: Удаление после завершения
  card.addEventListener('animationend', () => {
    card.style.willChange = 'auto';
    card.classList.remove('animating');
  }, { once: true });
});

Scroll-triggered: only when near viewport

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // Готовимся к анимации при появлении
        entry.target.style.willChange = 'transform, opacity';
      } else {
        // Убираем layer для невидимых элементов
        entry.target.style.willChange = 'auto';
      }
    });
  },
  { rootMargin: '200px' } // Готовим за 200px до viewport
);

document.querySelectorAll('.animated').forEach(el => observer.observe(el));

Что НЕ работает с will-change

/* will-change только для свойств без layout */
/* ХОРОШО: compositor-only properties */
.el { will-change: transform; }    /* GPU layer ✓ */
.el { will-change: opacity; }      /* GPU layer ✓ */
.el { will-change: filter; }       /* GPU layer ✓ */

/* ПЛОХО: вызывает layout — will-change не помогает */
.el { will-change: width; }        /* Требует relayout */
.el { will-change: margin; }       /* Требует relayout */
.el { will-change: top; }          /* Используй transform: translateY */

Память: стоимость compositor layer

/* Каждый will-change element = compositor layer = память GPU */
/* Для изображения 1920x1080: ~8MB GPU памяти */

/* ПЛОХО: will-change на всех элементах */
* { will-change: transform; } /* Съест всю VRAM */

/* ХОРОШО: только на том, что реально анимируется */
.header.sticky { will-change: transform; }

Частые ошибки

  • will-change на всех элементах — истощает память GPU, может замедлить
  • will-change: auto на анимируемых элементах — не даёт никакого эффекта
  • Использование вместо transform для позиционирования — переносите на transform/opacity
  • Не удалять will-change после анимации — layer остаётся в памяти навсегда

Связанные темы

Ресурсы