Как анимированные паттерны фона не сбрасываются
У части паттернов в генераторе фона есть анимированная версия - иконка рядом с названием паттерна переключает её на превью, а затем и на всём сайте. Если вы вставляете такой узор себе на проект и сами меняете его цвет или прозрачность динамически - полезно понимать три вещи, из-за которых подобная анимация обычно дёргается и перезапускается на ровном месте.
Анимация живёт в самой картинке, а не в DOM-элементе
Анимированный узор - это SVG с встроенной анимацией (CSS-кадрами или SMIL-тегом animate), обёрнутый в data-URI и подставленный как фоновая картинка. Браузер не знает, что вчера здесь была «почти такая же» картинка - он сверяет саму строку URL. Если она изменилась хоть на один символ, ресурс считается новым: загружается и рисуется с нуля, а вместе с ним заново стартует и анимация внутри. DOM-элемент, на который повешен фон, при этом может вообще не меняться - дело не в React и не в перерисовке компонента, а конкретно в идентичности строки картинки.
SVG-разметка обязана быть чистой функцией
Если генерация SVG зависит от чего-то переменного - например, случайной задержки анимации, - то одинаковые на вид параметры каждый раз дают разную строку на выходе. Любой лишний повторный вызов такой функции (а их в реальном приложении случается больше, чем кажется - например, при синхронизации состояния с URL) тихо подсовывает новую data-URI и рестартует анимацию без всякой видимой причины. Правило простое: одинаковый вход обязан давать байт-в-байт одинаковый SVG на выходе. Никакого Math.random(), Date.now() и подобного внутри генератора разметки.
Не запекайте то, что может часто меняться
Слайдер прозрачности или смена акцентного цвета темы - это события, которые стреляют часто и меняют реальные значения. Если цвет и прозрачность зашиты прямо в SVG-разметку, каждое такое событие честно генерирует новую картинку - и анимация дёргается на каждый тик. Разделите «форму» и «цвет»: рисуйте узор один раз чёрно-белой маской (её URL зависит только от формы и того, включена ли анимация), а реальный цвет и прозрачность накладывайте поверх обычными CSS-свойствами - их браузер умеет менять мгновенно, не трогая уже загруженный ресурс маски.
background-color: var(--bg);
.pattern {
background-color: var(--pattern-color);
opacity: var(--pattern-opacity);
mask-image: var(--pattern-mask); /* меняется редко: форма + анимация */
mask-mode: alpha;
}Слой фона должен пережить переход между страницами
Если фон рендерится внутри той части дерева компонентов, которая целиком заменяется при переходе на другую страницу (типичная ситуация для многостраничных SPA), DOM-узел с фоном каждый раз создаётся заново - та же проблема идентичности картинки, что и в первом пункте, только на уровне всего элемента. Держите такой слой в самом верхнем, ни от чего не зависящем месте дерева, которое монтируется один раз на весь сеанс и не пересоздаётся при переходах.
// Верхний уровень приложения - рендерится один раз за сессию
function AppShell({ children }) {
return (
<>
<BackgroundLayer />
{children}
</>
)
}Коротко
Три источника внезапного рестарта анимации: DOM-узел с фоном пересоздаётся при навигации; генератор SVG не является чистой функцией (например, использует случайные значения); цвет или прозрачность запечены в саму картинку и меняются вместе с обычными интерактивными действиями пользователя. Уберите все три - и анимация играет ровно так долго, как вы задумали, независимо от того, что происходит вокруг неё.