Отключение анимаций на слабых устройствах

Производительность анимаций в mo.js напрямую зависит от возможностей устройства, частоты кадров, нагрузки на главный поток и сложности сцены. При отсутствии адаптивной стратегии даже аккуратно настроенные эффекты приводят к просадкам FPS, перегреву и деградации пользовательского интерфейса.

Основная задача — не просто уменьшить нагрузку, а полностью исключить выполнение анимационного слоя там, где он не может быть отрисован стабильно.


Использование системных предпочтений пользователя

Первичный и наиболее корректный источник информации о необходимости отключения анимаций — системная настройка:

@media (prefers-reduced-motion: reduce) {
  /* отключение визуальных эффектов */
}

В JavaScript это отражается через MediaQueryList:

const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

При активации данного режима любые mo.js-эффекты должны либо не создаваться, либо заменяться статическим представлением.

Типовая стратегия:

  • полный пропуск инициализации timeline
  • замена burst-эффектов статичными SVG/PNG
  • отключение spring-анимаций и physics-based движений

Оценка аппаратных ограничений устройства

Системные настройки не всегда отражают реальную производительность. Поэтому дополнительно используется проверка характеристик устройства.

Ограничения памяти

const lowMemory = navigator.deviceMemory && navigator.deviceMemory < 4;

Значение deviceMemory предоставляет грубую оценку доступной оперативной памяти. Устройства с 2–4 GB RAM часто не выдерживают интенсивных анимаций.


Количество ядер процессора

const lowCPU = navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4;

Низкое количество потоков CPU напрямую влияет на выполнение JavaScript и композитинг анимаций.


Комбинированная оценка слабого устройства

const isLowEndDevice =
  (navigator.deviceMemory && navigator.deviceMemory < 4) ||
  (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 4);

Такая проверка используется как базовый фильтр перед запуском mo.js-сцен.


Динамическое отключение mo.js-анимаций

mo.js не имеет встроенного глобального переключателя производительности, поэтому контроль реализуется на уровне архитектуры приложения.

Глобальный флаг управления анимациями

const shouldDisableAnimations =
  window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
  isLowEndDevice;

Данный флаг становится центральной точкой принятия решений.


Архитектура «animation gate»

Все анимации проходят через единый шлюз инициализации.

function runMojsAnimation(factory) {
  if (shouldDisableAnimations) {
    factory?.staticRender?.();
    return null;
  }

  return factory();
}

Такой подход исключает необходимость проверок в каждом компоненте и обеспечивает единообразное поведение.


Пример условного создания burst-анимации

import mojs from 'mo-js';

function createBurst() {
  return new mojs.Burst({
    radius: { 0: 100 },
    count: 10,
    children: {
      shape: 'circle',
      radius: 5,
      fill: 'cyan'
    }
  });
}

const burst = shouldDisableAnimations
  ? null
  : createBurst();

if (burst) {
  burst.play();
}

При слабых устройствах объект не создаётся вовсе, исключая лишнюю нагрузку на GC и RAF.


Отключение уже запущенных анимаций

mo.js использует timeline и отдельные анимационные сущности, которые необходимо корректно останавливать.

Остановка timeline

timeline.stop();
timeline.reset();

Пауза активных анимаций

animation.pause();

Однако при глобальном отключении предпочтительнее уничтожение ссылок:

animation = null;
timeline = null;

Это позволяет сборщику мусора освободить ресурсы.


Стратегия деградации интерфейса

При отключении mo.js анимации заменяются статическими состояниями:

  • burst → статичный SVG-эффект
  • transition → мгновенное изменение opacity
  • spring → фиксированное позиционирование
  • path motion → линейное перемещение без интерполяции

Пример замены:

function showSuccess() {
  if (shouldDisableAnimations) {
    element.classList.add('state-success-static');
    return;
  }

  const burst = new mojs.Burst({
    radius: { 0: 120 },
    count: 12
  });

  burst.play();
}

Использование requestAnimationFrame budget control

Дополнительная защита заключается в контроле частоты кадров:

let lastFrameTime = performance.now();

function tick(now) {
  const delta = now - lastFrameTime;

  if (delta > 32) {
    shouldDisableAnimations = true;
  }

  lastFrameTime = now;
  requestAnimationFrame(tick);
}

requestAnimationFrame(tick);

При стабильных просадках ниже ~30 FPS система автоматически переводится в режим без анимаций.


Ленивое создание анимаций

Инициализация mo.js-объектов откладывается до момента, когда есть подтверждённая производительность:

let burst;

function getBurst() {
  if (shouldDisableAnimations) return null;

  if (!burst) {
    burst = new mojs.Burst({
      radius: { 0: 80 },
      count: 8
    });
  }

  return burst;
}

Такой подход снижает нагрузку на стартовую загрузку страницы и уменьшает количество объектов в памяти.


Интеграция с системой состояний приложения

Управление анимациями выносится в единый конфигурационный модуль:

export const motionConfig = {
  enabled: !shouldDisableAnimations,
  intensity: shouldDisableAnimations ? 0 : 1
};

Далее все эффекты привязываются к этому слою:

if (motionConfig.enabled) {
  animation.play();
}

Учет масштабируемости сцены

При сложных сценах mo.js (например, множественные burst + timeline + shape animations) отключение должно быть каскадным:

  • отключение дочерних эффектов
  • остановка родительского timeline
  • удаление обработчиков событий animationend
function disableScene(scene) {
  scene.timelines?.forEach(t => t.stop());
  scene.animations?.forEach(a => a.pause());
  scene.destroy?.();
}

Оптимизация через сокращение параметров вместо полного отключения

Иногда полное отключение не требуется, достаточно упрощения:

const config = shouldDisableAnimations
  ? {
      radius: { 0: 20 },
      count: 3,
      duration: 200
    }
  : {
      radius: { 0: 120 },
      count: 12,
      duration: 600
    };

Такой подход сохраняет визуальную обратную связь при минимальной нагрузке.


Контроль повторного запуска анимаций

На слабых устройствах важно предотвращать повторные триггеры:

let locked = false;

function triggerAnimation() {
  if (shouldDisableAnimations || locked) return;

  locked = true;

  const anim = createBurst();
  anim.play();

  setTimeout(() => {
    locked = false;
  }, 500);
}

Это снижает вероятность накопления очереди анимаций в RAF.


Итоговая модель поведения системы

Поведение системы анимаций можно описать как многоуровневый фильтр:

  • системные предпочтения пользователя
  • аппаратные ограничения
  • runtime-профилирование FPS
  • централизованный animation gate
  • деградация эффектов или полное отключение

Такая архитектура обеспечивает стабильную работу mo.js даже в условиях ограниченных ресурсов без перегрузки рендеринга и блокировки основного потока.