Интерактивные анимации строятся вокруг реакции на действия пользователя или изменения состояния интерфейса. Основная идея заключается в том, что анимация не запускается автоматически, а привязывается к событиям DOM: кликам, наведению курсора, прокрутке, вводу с клавиатуры и касаниям на мобильных устройствах.
В mo.js анимационные сущности (tween, timeline, shape, burst) изначально отделены от источника запуска. Это позволяет переиспользовать один и тот же объект анимации в разных сценариях, инициируя его через обработчики событий.
Ключевая особенность событийной модели заключается в том, что логика интерфейса и визуальная реакция разделяются. Событие становится триггером, а анимация — реакцией.
Наиболее прямой способ взаимодействия — привязка метода
.play() к обработчику события.
const circle = new mojs.Shape({
shape: 'circle',
radius: 30,
fill: '#4F46E5',
duration: 600,
easing: 'cubic.out'
});
const button = document.querySelector('.button');
button.addEventListener('click', () => {
circle.play();
});
Такой подход подходит для простых сценариев, где анимация запускается однократно и не требует сложной синхронизации.
При многократных событиях важно учитывать, что анимация должна корректно перезапускаться.
const burst = new mojs.Burst({
radius: { 0: 80 },
count: 12,
children: {
shape: 'polygon',
radius: { 6: 0 },
fill: '#10B981',
duration: 700
}
});
document.addEventListener('click', (e) => {
burst
.tune({ x: e.pageX, y: e.pageY })
.replay();
});
Метод replay() обеспечивает сброс состояния и повторное
воспроизведение без создания нового экземпляра.
Интерактивные эффекты часто требуют позиционирования относительно точки взаимодействия.
const burst = new mojs.Burst({
radius: { 0: 100 },
count: 10,
children: {
shape: 'circle',
radius: { 8: 0 },
fill: '#F59E0B',
duration: 800
}
});
window.addEventListener('click', (e) => {
const x = e.clientX;
const y = e.clientY;
burst.tune({
x,
y
}).replay();
});
Использование tune() позволяет динамически изменять
параметры перед запуском анимации без пересоздания объекта.
События mouseenter и mouseleave позволяют
создавать реактивные эффекты интерфейса.
const hoverShape = new mojs.Shape({
shape: 'rect',
radius: 40,
fill: '#3B82F6',
scale: { 1: 1.2 },
duration: 300,
easing: 'quad.out'
});
const box = document.querySelector('.box');
box.addEventListener('mouseenter', () => {
hoverShape.replay();
});
box.addEventListener('mouseleave', () => {
hoverShape.replayBackward();
});
Использование replayBackward() позволяет возвращать
элемент в исходное состояние без отдельной анимации.
Для сложных сценариев применяется Timeline, позволяющий
объединять несколько анимаций в цепочку и управлять ими как единым
объектом.
const scaleUp = new mojs.Tween({
duration: 300,
onUpdate: (progress) => {
button.style.transform = `scale(${1 + progress * 0.2})`;
}
});
const scaleDown = new mojs.Tween({
duration: 300,
onUpdate: (progress) => {
button.style.transform = `scale(${1.2 - progress * 0.2})`;
}
});
const timeline = new mojs.Timeline({
repeat: 0
});
timeline.add(scaleUp, scaleDown);
button.addEventListener('mousedown', () => timeline.replay());
button.addEventListener('mouseup', () => timeline.replayBackward());
Timeline позволяет синхронизировать несколько эффектов, включая shape, tween и burst.
Прокрутка страницы часто используется для постепенного раскрытия визуальных эффектов.
const progressShape = new mojs.Shape({
shape: 'circle',
radius: 50,
stroke: '#EF4444',
strokeWidth: { 0: 10 },
fill: 'none',
duration: 1
});
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY;
const maxScroll = document.body.scrollHeight - window.innerHeight;
const progress = scrollTop / maxScroll;
progressShape.progress(progress).play();
});
Метод progress() позволяет синхронизировать анимацию с
внешним состоянием, а не с таймером.
Интерактивность может быть усилена реакцией на перемещение мыши.
const follower = new mojs.Shape({
shape: 'circle',
radius: 20,
fill: '#8B5CF6',
isShowStart: true
});
window.addEventListener('mousemove', (e) => {
follower.tune({
x: e.clientX,
y: e.clientY
}).replay();
});
Для повышения производительности часто используется throttling событий, чтобы уменьшить частоту обновлений.
На мобильных устройствах используются события
touchstart, touchmove,
touchend.
document.addEventListener('touchstart', (e) => {
const touch = e.touches[0];
new mojs.Burst({
x: touch.clientX,
y: touch.clientY,
radius: { 0: 90 },
count: 8,
children: {
shape: 'zigzag',
stroke: '#22C55E',
duration: 600
}
}).play();
});
Touch-интеракции требуют учета многопальцевого ввода и предотвращения стандартного поведения браузера при необходимости.
Интерактивные эффекты часто зависят от логического состояния UI.
let active = false;
const indicator = new mojs.Shape({
shape: 'circle',
radius: 25,
fill: '#EF4444',
scale: { 1: 1.5 },
duration: 400
});
button.addEventListener('click', () => {
active = !active;
if (active) {
indicator.replay();
} else {
indicator.replayBackward();
}
});
Такой подход позволяет связывать визуальные эффекты с состояниями приложения без жесткой привязки к DOM.
При работе с динамически создаваемыми элементами применяется делегирование событий.
document.body.addEventListener('click', (e) => {
if (e.target.classList.contains('item')) {
new mojs.Burst({
x: e.clientX,
y: e.clientY,
radius: { 0: 70 },
count: 6,
children: {
shape: 'cross',
stroke: '#3B82F6',
duration: 500
}
}).play();
}
});
Этот подход снижает количество обработчиков и упрощает масштабирование интерфейса.
При высокой частоте событий возникает необходимость контроля производительности:
requestAnimationFrame для синхронизации
обновленийtune() и
replay()Пример throttling:
let last = 0;
window.addEventListener('mousemove', (e) => {
const now = Date.now();
if (now - last < 16) return;
last = now;
burst.tune({
x: e.clientX,
y: e.clientY
}).replay();
});
Сложные интерфейсы объединяют разные типы событий для управления одной анимацией.
const complex = new mojs.Timeline();
complex.add(
new mojs.Shape({
shape: 'circle',
radius: 40,
fill: '#F43F5E',
duration: 300
}),
new mojs.Shape({
shape: 'circle',
radius: { 40: 0 },
stroke: '#F43F5E',
duration: 600
})
);
const area = document.querySelector('.area');
area.addEventListener('mouseenter', () => complex.replay());
area.addEventListener('mouseleave', () => complex.replayBackward());
Помимо стандартных DOM-событий используются кастомные события, создаваемые логикой приложения.
document.addEventListener('item:added', (e) => {
new mojs.Burst({
x: e.detail.x,
y: e.detail.y,
radius: { 0: 60 },
count: 5,
children: {
shape: 'rect',
fill: '#6366F1',
duration: 500
}
}).play();
});
Кастомные события позволяют отделить бизнес-логику от визуального слоя и масштабировать систему анимаций независимо от UI.