Custom shapes

Библиотека mo.js предоставляет возможность создавать не только стандартные фигуры (круги, прямоугольники, линии), но и полностью кастомные формы. Это ключевой механизм для построения сложных анимаций, логотипов, и уникальных визуальных эффектов.

Пользовательские формы реализуются через расширение базового класса mojs.CustomShape, который позволяет определить собственную SVG-геометрию.


Базовый принцип работы

В основе пользовательской формы лежит метод getShape(), который должен возвращать строку SVG-разметки. Именно она используется для отрисовки фигуры.

const MyShape = mojs.CustomShape({
  getShape () {
    return '<path d="M0,0 L100,0 L50,100 Z" />';
  }
});

После определения форма регистрируется под уникальным именем:

mojs.addShape('myShape', MyShape);

Теперь её можно использовать как обычную фигуру:

new mojs.Shape({
  shape: 'myShape',
  fill: 'red'
});

Структура SVG внутри CustomShape

Метод getShape() возвращает строку, содержащую SVG-элемент. Чаще всего используются:

  • <path> — для сложных форм
  • <circle> — круги
  • <rect> — прямоугольники
  • <polygon> — многоугольники

Пример с polygon:

const StarShape = mojs.CustomShape({
  getShape () {
    return '<polygon points="50,0 61,35 98,35 68,57 79,91 50,70 21,91 32,57 2,35 39,35" />';
  }
});

Работа с системой координат

mo.js использует собственную систему координат внутри SVG. По умолчанию:

  • Центр фигуры: (0, 0)
  • Размер определяется параметром radius

Важно учитывать, что все координаты в SVG должны быть нормализованы относительно центра.

Пример неправильной формы:

return '<rect x="0" y="0" width="100" height="100" />';

Такая фигура будет смещена. Правильный вариант:

return '<rect x="-50" y="-50" width="100" height="100" />';

Масштабирование формы

Размер пользовательской фигуры контролируется через параметр radius, как и у стандартных фигур.

new mojs.Shape({
  shape: 'myShape',
  radius: 50
});

Важно:

  • SVG внутри getShape() должен быть рассчитан примерно на диапазон [-50, 50]
  • mo.js автоматически масштабирует форму под указанный radius

Использование transform внутри SVG

Допускается использование SVG-трансформаций:

const RotatedShape = mojs.CustomShape({
  getShape () {
    return '<rect x="-50" y="-50" width="100" height="100" transform="rotate(45)" />';
  }
});

Однако чаще рекомендуется управлять трансформациями через mo.js:

new mojs.Shape({
  shape: 'rotatedShape',
  angle: { 0: 360 }
});

Это позволяет синхронизировать анимацию с таймлайном.


Параметр viewBox

mo.js автоматически создает SVG-контейнер, но иногда требуется явно задать viewBox. Это делается через статические свойства:

const Custom = mojs.CustomShape({
  getShape () {
    return '<path d="M0 0 L100 0 L50 100 Z" />';
  },
  viewBox: '0 0 100 100'
});

Если viewBox не задан, mo.js пытается вычислить его автоматически, что может привести к некорректному масштабированию.


Анимация пользовательских форм

После регистрации кастомная форма полностью поддерживает систему анимации mo.js:

new mojs.Shape({
  shape: 'myShape',
  fill: 'none',
  stroke: 'blue',
  strokeWidth: { 0: 10 },
  scale: { 0: 1 },
  duration: 1000
});

Поддерживаются:

  • scale
  • rotate (angle)
  • opacity
  • strokeDashoffset
  • fill, stroke

Анимация пути (stroke animation)

Для сложных форм часто используется анимация обводки:

new mojs.Shape({
  shape: 'myShape',
  fill: 'none',
  stroke: 'black',
  strokeWidth: 2,
  strokeDasharray: '100%',
  strokeDashoffset: { '100%': '0%' }
});

Это создает эффект “рисования” формы.


Композиция сложных фигур

Внутри getShape() можно возвращать сразу несколько SVG-элементов:

const ComplexShape = mojs.CustomShape({
  getShape () {
    return `
      <g>
        <circle cx="0" cy="0" r="30" />
        <rect x="-10" y="-10" width="20" height="20" />
      </g>
    `;
  }
});

Группа <g> позволяет объединять элементы в одну форму.


Динамическая генерация формы

Форма может зависеть от параметров:

const DynamicShape = mojs.CustomShape({
  getShape () {
    const size = 50;
    return `<rect x="-${size/2}" y="-${size/2}" width="${size}" height="${size}" />`;
  }
});

Однако стоит учитывать, что getShape() вызывается один раз при создании.


Использование кривых Безье

Для создания сложных форм применяется <path> с кривыми:

const CurveShape = mojs.CustomShape({
  getShape () {
    return `
      <path d="
        M -50 0
        Q 0 -50 50 0
        T 150 0
      " />
    `;
  }
});

Это позволяет создавать плавные органические формы.


Оптимизация производительности

При работе с пользовательскими формами важно учитывать:

1. Минимизация сложности SVG

  • избегать избыточных точек
  • оптимизировать path

2. Повторное использование форм

  • регистрировать один раз через mojs.addShape
  • не создавать новые классы внутри анимаций

3. Ограничение количества элементов

  • большое количество сложных SVG снижает FPS

Расширенные техники

Использование clipPath

const ClippedShape = mojs.CustomShape({
  getShape () {
    return `
      <svg>
        <defs>
          <clipPath id="clip">
            <circle cx="0" cy="0" r="30" />
          </clipPath>
        </defs>
        <rect x="-50" y="-50" width="100" height="100" clip-path="url(#clip)" />
      </svg>
    `;
  }
});

Маски (mask)

const MaskShape = mojs.CustomShape({
  getShape () {
    return `
      <svg>
        <mask id="mask">
          <rect x="-50" y="-50" width="100" height="100" fill="white"/>
          <circle cx="0" cy="0" r="20" fill="black"/>
        </mask>
        <rect x="-50" y="-50" width="100" height="100" mask="url(#mask)" />
      </svg>
    `;
  }
});

Интеграция с Timeline

Пользовательские формы полностью совместимы с mojs.Timeline:

const shape = new mojs.Shape({
  shape: 'myShape',
  scale: { 0: 1 }
});

const timeline = new mojs.Timeline();
timeline.add(shape);
timeline.play();

Частые ошибки

Смещение фигуры

  • причина: координаты не центрированы

Неправильный масштаб

  • причина: отсутствует viewBox

Форма не отображается

  • ошибка в SVG-разметке

Анимация не применяется

  • свойства заданы внутри SVG, а не через mo.js

Практический пример: кастомная стрелка

const Arrow = mojs.CustomShape({
  getShape () {
    return `
      <path d="
        M -50 0
        L 20 0
        L 20 -20
        L 50 0
        L 20 20
        L 20 0
      " />
    `;
  }
});

mojs.addShape('arrow', Arrow);

new mojs.Shape({
  shape: 'arrow',
  stroke: 'black',
  fill: 'none',
  strokeWidth: 4,
  scale: { 0: 1 },
  angle: { 0: 360 }
});

Практический пример: пульсирующая звезда

const Star = mojs.CustomShape({
  getShape () {
    return '<polygon points="50,0 61,35 98,35 68,57 79,91 50,70 21,91 32,57 2,35 39,35" />';
  }
});

mojs.addShape('star', Star);

new mojs.Shape({
  shape: 'star',
  fill: 'yellow',
  scale: { 0.5: 1.2 },
  yoyo: true,
  repeat: 999
});

Архитектурные особенности

  • CustomShape — фабрика, возвращающая класс
  • addShape — регистрация в глобальном реестре
  • Shape — универсальный контейнер для анимации

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