Библиотека построена вокруг модульной системы, где каждый визуальный
или анимационный элемент инкапсулируется в отдельный класс. Базовый слой
включает mojs.Module, который задаёт общий контракт для
всех анимационных сущностей: управление состоянием, параметризацию,
жизненный цикл и интеграцию с движком тайминга.
Ключевая идея — любой новый визуальный эффект может быть реализован как расширение существующей абстракции, без вмешательства в ядро.
Внутри системы выделяются три уровня расширения:
Такой подход позволяет собирать сложные анимации как из конструктора.
Каждый модуль в системе описывается через набор стандартных элементов:
Упрощённая модель:
class BaseModule {
constructor(options = {}) {
this._props = options;
this._progress = 0;
this._isActive = false;
this._init();
}
_init() {}
play() {
this._isActive = true;
this._render();
}
pause() {
this._isActive = false;
}
_render() {}
_update(progress) {
this._progress = progress;
this._render();
}
}
Внутри mo.js этот контракт расширен дополнительными слоями: интерполяцией значений, системой tweening и встроенной синхронизацией времени.
Основной механизм расширения — наследование от
mojs.Module. Это базовый строительный блок для всех
анимаций.
const MojsModule = mojs.Module;
class CustomModule extends MojsModule {
constructor(options = {}) {
super(options);
this.radius = this._props.radius || 50;
this.color = this._props.color || '#ff0000';
}
_declareDefaults() {
return {
radius: 50,
color: '#ff0000'
};
}
_render() {
const progress = this._props.progress || 0;
const currentRadius = this.radius * progress;
this.el.style.transform = `scale(${currentRadius / this.radius})`;
this.el.style.background = this.color;
}
}
Метод _declareDefaults является ключевым элементом: он
формирует декларативную модель параметров, которая затем автоматически
объединяется с пользовательскими значениями.
Внутри архитектуры mo.js параметры не просто передаются в конструктор — они проходят через систему нормализации и интерполяции.
Каждое значение может быть:
{0, 100}Пример декларации:
const module = new CustomModule({
radius: { 0: 10, 1: 80 },
color: '#00ffcc',
duration: 1200
});
При этом система автоматически строит tween:
radius(t) = 10 + (80 - 10) * easing(t)
Одной из наиболее мощных возможностей является регистрация собственных shape-объектов.
Фигуры в mo.js — это SVG-описания, которые динамически интерполируются.
Базовая структура:
mojs.addShape('triangle', class TriangleShape extends mojs.CustomShape {
getShape() {
return '<path d="M50 0 L100 100 L0 100 Z" />';
}
});
После регистрации фигура становится доступной во всех shape-модулях:
const burst = new mojs.Burst({
radius: { 0: 100 },
count: 6,
children: {
shape: 'triangle',
fill: 'cyan'
}
});
Каждый модуль проходит несколько стадий:
_init)Расширение логики возможно на любом этапе:
class AdvancedModule extends mojs.Module {
_init() {
this._createElement();
this._setupTimeline();
}
_createElement() {
this.el = document.createElement('div');
this.el.className = 'custom-module';
}
_update(progress) {
super._update(progress);
this._applyPhysics(progress);
}
_applyPhysics(progress) {
const bounce = Math.sin(progress * Math.PI);
this.el.style.transform = `translateY(${bounce * 50}px)`;
}
}
Сильная сторона системы — возможность комбинировать модули в сложные сцены.
Композиция строится через контейнерные объекты:
const circle = new mojs.Shape({
shape: 'circle',
fill: 'yellow'
});
const square = new mojs.Shape({
shape: 'rect',
fill: 'blue'
});
const timeline = new mojs.Timeline();
timeline.add(circle, square);
При создании собственного модуля важно учитывать возможность его использования внутри Timeline.
Кастомные модули часто используются для создания эффектов, выходящих за пределы стандартного набора: физические взаимодействия, нестандартная интерполяция, синхронизация с внешними событиями.
Пример модуля с физическим поведением:
class GravityModule extends mojs.Module {
constructor(options) {
super(options);
this.velocity = 0;
this.gravity = options.gravity || 0.5;
}
_update(progress) {
this.velocity += this.gravity;
const y = this.velocity * progress;
this.el.style.transform = `translateY(${y}px)`;
}
}
Такая модель позволяет интегрировать простую физику без внешних движков.
Метод _render является центральной точкой расширения. Он
вызывается на каждом кадре и отвечает за синхронизацию состояния и
визуального представления.
class RenderModule extends mojs.Module {
_render() {
const p = this._props.progress;
const scale = 1 + p * 2;
const rotate = p * 360;
this.el.style.transform =
`scale(${scale}) rotate(${rotate}deg)`;
}
}
Рендер не должен содержать тяжёлых вычислений — архитектура предполагает предварительную нормализацию данных.
Модули могут реагировать на внешние сигналы:
class EventModule extends mojs.Module {
constructor(options) {
super(options);
window.addEventListener('click', this._onClick.bind(this));
}
_onClick() {
this.replay();
}
}
Такой подход позволяет связывать анимации с пользовательским вводом или состоянием приложения.
Внутренняя архитектура поддерживает композицию поведения через миксины.
const Draggable = {
_initDrag() {
this.el.addEventListener('mousedown', this._startDrag.bind(this));
}
};
class DragModule extends mojs.Module {
constructor(options) {
super(options);
this._initDrag();
}
}
Object.assign(DragModule.prototype, Draggable);
Миксины позволяют избегать глубоких цепочек наследования и создавать повторно используемые блоки логики.
Система tweening в mo.js может быть расширена кастомными функциями интерполяции.
mojs.easing.customBounce = function (t) {
return Math.abs(Math.sin(6.28 * t) * (1 - t));
};
Использование:
const anim = new mojs.Shape({
radius: { 0: 80 },
easing: 'customBounce'
});
Кастомные модули могут работать как с DOM-элементами, так и с SVG-структурами. Выбор модели влияет на производительность и гибкость.
SVG-модули предпочтительны для:
DOM-модули используются для:
Состояние модуля обычно делится на:
class StateModule extends mojs.Module {
_init() {
this._state = {
phase: 0,
direction: 1
};
}
_update(progress) {
this._state.phase += this._state.direction * 0.1;
if (this._state.phase > 1) {
this._state.direction = -1;
}
}
}
При проектировании сложных систем поверх mo.js используется подход многоуровневых модулей:
Такая декомпозиция позволяет строить системы, где каждая часть отвечает за строго ограниченную зону ответственности.
Производительность критична при работе с большим количеством анимационных сущностей.
Основные принципы:
_update_render() {
if (this._cachedProgress === this._progress) return;
this._cachedProgress = this._progress;
this.el.style.transform =
`translateX(${this._progress * 200}px)`;
}
Сложные анимации не реализуются в одном модуле — они разбиваются на несколько независимых сущностей:
Затем объединяются через Timeline или композиционный контейнер.
const move = new MoveModule({ duration: 1000 });
const color = new ColorModule({ duration: 1000 });
new mojs.Timeline().add(move, color).play();