В основе расширяемости mo.js лежит модульная система классов, построенная вокруг базовых сущностей анимационного движка. Почти каждый визуальный элемент — это наследник общего поведенческого слоя, который управляет жизненным циклом анимации, интерполяцией свойств и отрисовкой. Расширение базовых классов позволяет создавать собственные анимационные примитивы, комбинировать поведение существующих объектов и внедрять новые типы визуализации без изменения ядра библиотеки.
mo.js использует классическую схему наследования ES6 поверх собственной архитектуры модулей. Основные классы, которые чаще всего расширяются:
mojs.Module — базовый слой поведения всех анимационных
сущностейmojs.CustomShape — расширение для создания новых
формmojs.Shape — готовый набор геометрических
примитивовmojs.Tween — управление интерполяцией свойствmojs.Html — анимация DOM-элементовНаследование строится через extends, но важно учитывать,
что внутренняя система mo.js опирается не только на прототипы
JavaScript, но и на собственные хуки жизненного цикла.
class MyShape extends mojs.Shape {
constructor(options = {}) {
super(options);
}
}
Однако простое наследование недостаточно — ключевая логика задаётся через переопределение внутренних методов.
Каждый модуль mo.js проходит фиксированные этапы:
Для расширения поведения используются защищённые методы (с префиксом
_), которые принято переопределять в дочерних классах.
Метод _declareDefaults используется для задания базовых
параметров модуля:
class PulseCircle extends mojs.Shape {
_declareDefaults() {
super._declareDefaults();
this._defaults.radius = 0;
this._defaults.strokeWidth = 2;
this._defaults.color = '#FF4D5A';
}
}
Внутри _defaults формируется исходное состояние объекта,
которое затем смешивается с пользовательскими параметрами.
mo.js активно использует промежуточные вычисления для анимационных
значений. Метод _calcProps позволяет определить производные
параметры перед каждым кадром.
class WaveShape extends mojs.Shape {
_calcProps() {
super._calcProps();
this._waveAmplitude = this.radius * 0.3;
this._waveFrequency = this.options.frequency || 2;
}
}
Такой подход позволяет отделить входные параметры от вычисляемых значений, что особенно важно при сложных анимациях.
Одним из ключевых механизмов расширения является переопределение
_render. Этот метод отвечает за визуальное представление
объекта на каждом кадре.
class GlowShape extends mojs.Shape {
_render() {
super._render();
const ctx = this.el.getContext('2d');
ctx.save();
ctx.shadowBlur = 20;
ctx.shadowColor = this.color;
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
}
Важно учитывать, что _render вызывается в
высокочастотном цикле, поэтому любые тяжёлые операции внутри него
критичны для производительности.
Для расширенных классов доступны хуки, позволяющие вмешиваться в ключевые моменты выполнения анимации:
_onStart — запуск анимации_onUpdate — обновление на каждом кадре_onComplete — завершениеclass TrailShape extends mojs.Shape {
_onStart() {
this.trail = [];
}
_onUpdate() {
this.trail.push({ x: this.x, y: this.y });
if (this.trail.length > 30) {
this.trail.shift();
}
}
_onComplete() {
this.trail = null;
}
}
Такой механизм используется для создания эффектов следа, частиц и сложных динамических систем.
Для создания новых геометрических форм применяется
mojs.CustomShape. Это специализированный класс, который
позволяет регистрировать собственную форму в системе рендера.
class StarShape extends mojs.CustomShape {
getShape() {
return '<path d="M10 15 L12 9 L14 15 L8 11 L16 11 Z" />';
}
}
После определения форма может быть зарегистрирована:
mojs.addShape('star', StarShape);
Теперь её можно использовать как стандартный shape:
new mojs.Shape({
shape: 'star',
radius: 40,
fill: 'yellow'
}).play();
Помимо наследования, mo.js активно использует композицию. Это позволяет расширять функциональность без глубокого переопределения базовой логики.
class CompositeShape extends mojs.Shape {
constructor(options = {}) {
super(options);
this.innerShape = new mojs.Shape({
parent: this.el,
radius: options.innerRadius || 20,
fill: options.innerFill || 'white'
});
}
_onUpdate() {
super._onUpdate();
this.innerShape.setProgress(this.progress);
}
}
Такой подход используется для построения сложных анимационных систем, состоящих из нескольких синхронизированных элементов.
mojs.Tween управляет интерполяцией значений во времени.
При расширении можно изменять поведение easing, кривых и
интерполяции.
class ElasticTween extends mojs.Tween {
_calcCurrentValue() {
const progress = super._calcCurrentValue();
return Math.sin(progress * Math.PI * 2) * Math.exp(-progress * 5);
}
}
Подобные расширения позволяют создавать нестандартные физические модели движения: пружины, колебания, затухающие системы.
mo.js активно использует соглашение об «условно приватных» методах:
_prefix — внутренние методы__doublePrefix — редко используемые системные хукиПри расширении классов важно:
super при переопределенииВместо жёсткого наследования часто используется смешивание поведения.
const ColorMixin = {
_changeColor(color) {
this._color = color;
this._applyColor();
}
};
class MixedShape extends mojs.Shape {
constructor(options) {
super(options);
Object.assign(this, ColorMixin);
}
}
Такой подход позволяет комбинировать независимые функциональные блоки: анимацию цвета, трансформации, поведение частиц.
mo.js использует систему декларативных свойств, которые автоматически интерполируются. При расширении можно добавлять новые параметры:
class SkewShape extends mojs.Shape {
_declareDefaults() {
super._declareDefaults();
this._defaults.skewX = 0;
this._defaults.skewY = 0;
}
_render() {
super._render();
this.el.style.transform =
`skew(${this.skewX}deg, ${this.skewY}deg)`;
}
}
Такие свойства становятся частью анимационного потока и могут изменяться через timeline.
Любой расширенный класс может быть встроен в
mojs.Timeline без дополнительных адаптеров.
const wave = new WaveShape({
radius: 50,
duration: 1000
});
const timeline = new mojs.Timeline({
children: [wave]
});
timeline.play();
Система автоматически управляет синхронизацией, если класс корректно реализует базовые методы жизненного цикла.
При создании собственных классов часто возникают проблемы:
super._declareDefaults() приводит к
потере базовых параметров_render вызывают просадку FPS_render ломает синхронизациюprogress и внутренних свойств
приводит к артефактамПри создании масштабных систем рекомендуется разделять логику:
class PhysicsModel {
update(v, a) {
return v + a;
}
}
class PhysicsShape extends mojs.Shape {
constructor(options) {
super(options);
this.model = new PhysicsModel();
}
_onUpdate() {
this.velocity = this.model.update(this.velocity, this.acceleration);
}
}
Такое разделение делает систему расширяемой и предсказуемой при увеличении сложности анимаций.