Расширение базовых классов

В основе расширяемости 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 проходит фиксированные этапы:

  1. Инициализация параметров
  2. Подготовка вычисляемых свойств
  3. Рендер кадра
  4. Обновление состояния
  5. Завершение анимации

Для расширения поведения используются защищённые методы (с префиксом _), которые принято переопределять в дочерних классах.

Инициализация дефолтов

Метод _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;
  }
}

Такой механизм используется для создания эффектов следа, частиц и сложных динамических систем.


Расширение через CustomShape

Для создания новых геометрических форм применяется 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();

Расширение Shape через композицию поведения

Помимо наследования, 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);
  }
}

Такой подход используется для построения сложных анимационных систем, состоящих из нескольких синхронизированных элементов.


Расширение tween-модели

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.


Интеграция расширенных классов в timeline

Любой расширенный класс может быть встроен в mojs.Timeline без дополнительных адаптеров.

const wave = new WaveShape({
  radius: 50,
  duration: 1000
});

const timeline = new mojs.Timeline({
  children: [wave]
});

timeline.play();

Система автоматически управляет синхронизацией, если класс корректно реализует базовые методы жизненного цикла.


Типичные ошибки при расширении

При создании собственных классов часто возникают проблемы:

  • пропуск вызова super._declareDefaults() приводит к потере базовых параметров
  • тяжёлые вычисления в _render вызывают просадку FPS
  • изменение DOM вне _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);
  }
}

Такое разделение делает систему расширяемой и предсказуемой при увеличении сложности анимаций.