Производительность анимаций в mo.js напрямую зависит от возможностей устройства, частоты кадров, нагрузки на главный поток и сложности сцены. При отсутствии адаптивной стратегии даже аккуратно настроенные эффекты приводят к просадкам FPS, перегреву и деградации пользовательского интерфейса.
Основная задача — не просто уменьшить нагрузку, а полностью исключить выполнение анимационного слоя там, где он не может быть отрисован стабильно.
Первичный и наиболее корректный источник информации о необходимости отключения анимаций — системная настройка:
@media (prefers-reduced-motion: reduce) {
/* отключение визуальных эффектов */
}
В JavaScript это отражается через MediaQueryList:
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
При активации данного режима любые mo.js-эффекты должны либо не создаваться, либо заменяться статическим представлением.
Типовая стратегия:
Системные настройки не всегда отражают реальную производительность. Поэтому дополнительно используется проверка характеристик устройства.
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 не имеет встроенного глобального переключателя производительности, поэтому контроль реализуется на уровне архитектуры приложения.
const shouldDisableAnimations =
window.matchMedia('(prefers-reduced-motion: reduce)').matches ||
isLowEndDevice;
Данный флаг становится центральной точкой принятия решений.
Все анимации проходят через единый шлюз инициализации.
function runMojsAnimation(factory) {
if (shouldDisableAnimations) {
factory?.staticRender?.();
return null;
}
return factory();
}
Такой подход исключает необходимость проверок в каждом компоненте и обеспечивает единообразное поведение.
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.stop();
timeline.reset();
animation.pause();
Однако при глобальном отключении предпочтительнее уничтожение ссылок:
animation = null;
timeline = null;
Это позволяет сборщику мусора освободить ресурсы.
При отключении mo.js анимации заменяются статическими состояниями:
Пример замены:
function showSuccess() {
if (shouldDisableAnimations) {
element.classList.add('state-success-static');
return;
}
const burst = new mojs.Burst({
radius: { 0: 120 },
count: 12
});
burst.play();
}
Дополнительная защита заключается в контроле частоты кадров:
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) отключение должно быть каскадным:
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.
Поведение системы анимаций можно описать как многоуровневый фильтр:
Такая архитектура обеспечивает стабильную работу mo.js даже в условиях ограниченных ресурсов без перегрузки рендеринга и блокировки основного потока.