Код анимаций на mo.js в реальных проектах быстро превращается в
трудно поддерживаемый набор связанных между собой эффектов: разрозненные
tween, дублирующиеся timeline, хаотично
заданные параметры easing и жестко зашитые значения DOM-элементов.
Рефакторинг в этом контексте означает не косметическую правку, а
переработку структуры анимационной системы так, чтобы она стала
предсказуемой, расширяемой и изолированной от UI-логики.
На практике код часто выглядит как последовательность одноразовых объектов:
const burst = new mojs.Burst({
parent: '.button',
radius: { 0: 100 },
count: 10,
children: {
shape: 'circle',
radius: 5,
fill: 'red'
}
});
const tween = new mojs.Tween({
duration: 800,
onUpdate: (p) => console.log(p)
});
burst.replay();
tween.play();
Основные проблемы подобного подхода:
С ростом количества эффектов код начинает повторять один и тот же паттерн создания экземпляров, что приводит к сильной связанности UI и анимационного слоя.
Первый шаг рефакторинга — отделение описания анимации от её запуска. mo.js по своей природе конфигурационный, но часто используется императивно.
Базовый подход — вынести конфигурации:
const ANIMATION_CONFIG = {
burst: {
radius: { 0: 120 },
count: 12,
children: {
shape: 'circle',
radius: 4,
fill: '#FF5252'
}
},
tween: {
duration: 700,
easing: 'ease.out'
}
};
Теперь создание объектов становится производным:
const burst = new mojs.Burst({
parent: '.button',
...ANIMATION_CONFIG.burst
});
const tween = new mojs.Tween(ANIMATION_CONFIG.tween);
Это уменьшает связность и позволяет централизованно управлять визуальным стилем анимаций.
Следующий уровень рефакторинга — создание фабрик, которые инкапсулируют логику построения объектов mo.js.
function createBurst(parent, overrides = {}) {
return new mojs.Burst({
parent,
radius: { 0: 100 },
count: 10,
children: {
shape: 'circle',
radius: 5,
fill: '#FF4081'
},
...overrides
});
}
Фабрики решают сразу несколько задач:
Для сложных систем целесообразно создавать отдельный модуль
animations/factory.js, где каждая функция отвечает за
конкретный тип эффекта.
Одна из ключевых проблем mo.js-кода — несогласованность таймингов.
Разные duration и delay делают анимации
визуально разрозненными.
Рефакторинг вводит единый тайминг-профиль:
const TIMING = {
fast: 300,
normal: 600,
slow: 1200
};
const EASING = {
standard: 'ease.out',
smooth: 'cubic.out',
sharp: 'ease.in'
};
Использование:
const tween = new mojs.Tween({
duration: TIMING.normal,
easing: EASING.smooth
});
Такой подход упрощает изменение общей динамики интерфейса без правки каждого эффекта.
mo.js активно использует Timeline, но в плохо
рефакторенных проектах таймлайны становятся монолитами:
const timeline = new mojs.Timeline();
timeline.add([
new mojs.Burst({...}),
new mojs.Shape({...}),
new mojs.Tween({...})
]);
Проблема возникает, когда весь сценарий анимации оказывается в одном месте. Рефакторинг предполагает разбиение на композиционные блоки:
function createClickAnimation(target) {
const burst = createBurst(target);
const scale = new mojs.Tween({
duration: 400,
onUpdate: (p) => {
target.style.transform = `scale(${1 + p * 0.2})`;
}
});
return new mojs.Timeline().add(burst, scale);
}
Теперь каждый таймлайн становится самостоятельной единицей поведения.
Частая ошибка — прямое управление DOM внутри анимаций. Это усложняет повторное использование и тестирование.
Плохой вариант:
new mojs.Tween({
onUpdate: (p) => {
document.querySelector('.button').style.opacity = p;
}
});
Рефакторинг предполагает передачу зависимостей:
function fade(element) {
return new mojs.Tween({
duration: 500,
onUpdate: (p) => {
element.style.opacity = p;
}
});
}
Теперь анимация не зависит от конкретного DOM-селектора и может применяться к любому элементу.
Сложные проекты требуют декларативного описания всех анимаций:
const animations = [
{
type: 'burst',
target: '.button',
config: { count: 8 }
},
{
type: 'fade',
target: '.label',
config: { duration: 500 }
}
];
Далее создается интерпретатор:
function runAnimation(def) {
switch(def.type) {
case 'burst':
return createBurst(def.target, def.config);
case 'fade':
return fade(document.querySelector(def.target));
}
}
Такой подход отделяет описание поведения от реализации и упрощает масштабирование системы.
mo.js анимации часто пересоздаются при каждом событии, что приводит к лишним аллокациям. Рефакторинг вводит кеширование:
const cache = new Map();
function getBurst(parent) {
if (!cache.has(parent)) {
cache.set(parent, createBurst(parent));
}
return cache.get(parent);
}
Это уменьшает нагрузку на GC и ускоряет повторные анимации.
Особое внимание требуется очистке анимаций. Без явного управления возникают утечки памяти:
const timeline = createClickAnimation(button);
button.addEventListener('click', () => {
timeline.replay();
});
После рефакторинга добавляется контроль состояния:
function attachAnimation(element, timeline) {
const handler = () => timeline.replay();
element.addEventListener('click', handler);
return () => {
element.removeEventListener('click', handler);
timeline.stop();
};
}
Теперь возможно корректное удаление анимаций при уничтожении компонентов.
В крупных системах полезно ввести единый интерфейс:
class AnimationService {
constructor() {
this.registry = new Map();
}
register(name, factory) {
this.registry.set(name, factory);
}
play(name, target, config) {
const factory = this.registry.get(name);
if (!factory) return;
return factory(target, config).play();
}
}
Использование:
const service = new AnimationService();
service.register('burst', createBurst);
service.play('burst', '.button', { count: 6 });
Такой слой изолирует mo.js от остальной архитектуры приложения.
Сложные визуальные эффекты лучше объединять в классы:
class ClickEffect {
constructor(element) {
this.element = element;
this.timeline = createClickAnimation(element);
}
play() {
this.timeline.replay();
}
destroy() {
this.timeline.stop();
this.element = null;
}
}
Это переводит анимации в объектную модель, где каждый эффект управляется как сущность.
При увеличении количества анимаций критично вводить единые стандарты:
Результатом становится система, в которой добавление новой анимации не требует модификации существующего кода, а только регистрации нового фабричного метода.
Рефакторинг mo.js-кода в таком формате переводит анимационный слой из набора разрозненных эффектов в структурированную систему, где каждый элемент обладает четкой зоной ответственности и предсказуемым поведением.