Модульная структура проекта

Модульная структура — ключевой фактор поддерживаемости и масштабируемости анимационных проектов на базе библиотеки mo.js. Разделение кода на логические части позволяет изолировать ответственность, упростить повторное использование анимаций и снизить связанность компонентов.

Основной подход заключается в разделении проекта на следующие уровни:

  • ядро анимаций (animation core)
  • компоненты анимации (animation components)
  • конфигурации и пресеты
  • утилиты
  • точки входа (entry points)

Каждый уровень реализуется в виде отдельных модулей (файлов или директорий), что соответствует принципам ES Modules.


Структура директорий

Пример типичной структуры проекта:

/src
  /animations
    burst.js
    shape.js
    timeline.js
  /components
    buttonAnimation.js
    loaderAnimation.js
  /presets
    colors.js
    easing.js
  /utils
    helpers.js
    math.js
  index.js

Описание:

  • animations/ — базовые анимационные сущности mo.js
  • components/ — композиции анимаций для UI-элементов
  • presets/ — наборы параметров
  • utils/ — вспомогательные функции
  • index.js — главный файл инициализации

Базовые модули анимации

Каждый тип анимации выносится в отдельный модуль. Это позволяет переиспользовать логику и легко изменять поведение.

Пример: модуль Burst-анимации

// /animations/burst.js
import mojs from 'mo-js';

export function createBurst(options = {}) {
  return new mojs.Burst({
    radius: { 0: 100 },
    count: 10,
    children: {
      shape: 'circle',
      fill: 'cyan',
      duration: 1500
    },
    ...options
  });
}

Особенности:

  • параметры по умолчанию
  • возможность расширения через options
  • изоляция логики создания анимации

Композиция анимаций

Компоненты объединяют несколько анимаций в единое поведение.

Пример: анимация кнопки

// /components/buttonAnimation.js
import { createBurst } from '../animations/burst.js';

export function animateButton(el) {
  const burst = createBurst({
    parent: el,
    left: 0,
    top: 0
  });

  el.addEventListener('click', () => {
    burst.replay();
  });
}

Ключевые аспекты:

  • компонент не знает деталей реализации burst
  • работает через API базового модуля
  • легко заменяется или расширяется

Использование Timeline как отдельного модуля

Сложные анимации удобно инкапсулировать в Timeline.

// /animations/timeline.js
import mojs from 'mo-js';

export function createTimeline(animations) {
  const timeline = new mojs.Timeline();

  animations.forEach(anim => timeline.add(anim));

  return timeline;
}

Применение:

import { createBurst } from './burst.js';
import { createTimeline } from './timeline.js';

const burst1 = createBurst({ x: 100 });
const burst2 = createBurst({ x: 200 });

const timeline = createTimeline([burst1, burst2]);
timeline.play();

Конфигурационные модули

Конфигурации выносятся в отдельные файлы для централизованного управления параметрами.

Пример: цвета

// /presets/colors.js
export const COLORS = {
  primary: '#00FFFF',
  secondary: '#FF00FF',
  accent: '#FFD700'
};

Пример: easing-функции

// /presets/easing.js
import mojs from 'mo-js';

export const EASING = {
  bounce: mojs.easing.bounce.out,
  elastic: mojs.easing.elastic.out
};

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

import { COLORS } from '../presets/colors.js';

fill: COLORS.primary

Утилитарные модули

Утилиты помогают избежать дублирования кода.

Пример: генерация случайных значений

// /utils/helpers.js
export function random(min, max) {
  return Math.random() * (max - min) + min;
}

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

import { random } from '../utils/helpers.js';

radius: { 0: random(50, 150) }

Точка входа

Главный файл связывает все модули.

// index.js
import { animateButton } from './components/buttonAnimation.js';

const button = document.querySelector('.btn');
animateButton(button);

Принципы организации модулей

1. Один модуль — одна ответственность

Каждый файл выполняет строго определённую задачу:

  • создание одной анимации
  • настройка одного набора параметров
  • выполнение одной утилитарной функции

2. Изоляция зависимостей

Модули не должны напрямую зависеть друг от друга без необходимости.

Неправильно:

import { COLORS } from '../presets/colors.js';
import { animateButton } from '../components/buttonAnimation.js';

Правильно:

  • базовые модули не зависят от компонентов
  • зависимости направлены сверху вниз

3. Расширяемость через параметры

Все анимации должны поддерживать расширение через аргументы:

export function createShape(options = {}) {
  return new mojs.Shape({
    fill: 'red',
    ...options
  });
}

4. Повторное использование

Модуль должен быть пригоден для использования в разных местах:

const burst1 = createBurst({ x: 50 });
const burst2 = createBurst({ x: 150 });

Работа с ES Modules

Современная структура проекта предполагает использование ES Modules:

Экспорт

export function createBurst() {}
export const COLORS = {};

Импорт

import { createBurst } from './animations/burst.js';

Группировка экспортов

Для удобства можно создавать агрегирующие модули.

// /animations/index.js
export { createBurst } from './burst.js';
export { createTimeline } from './timeline.js';

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

import { createBurst, createTimeline } from './animations/index.js';

Разделение на уровни абстракции

Низкий уровень

  • mojs.Shape
  • mojs.Burst
  • mojs.Tween

Средний уровень

  • функции создания анимаций

Высокий уровень

  • UI-компоненты
  • пользовательские сценарии

Инкапсуляция логики

Модули должны скрывать внутреннюю реализацию:

export function createLoader() {
  const circle = new mojs.Shape({...});
  const burst = new mojs.Burst({...});

  return new mojs.Timeline().add(circle, burst);
}

Пользователь модуля работает только с результатом.


Динамическая загрузка модулей

Для оптимизации можно использовать lazy-loading:

button.addEventListener('click', async () => {
  const { animateButton } = await import('./components/buttonAnimation.js');
  animateButton(button);
});

Масштабирование проекта

При росте проекта структура усложняется:

/src
  /core
  /animations
    /basic
    /complex
  /components
    /ui
    /effects
  /presets
  /utils

Подходы к именованию

  • функции: createBurst, animateButton
  • файлы: burst.js, buttonAnimation.js
  • константы: COLORS, EASING

Разделение логики и представления

mo.js не работает напрямую с DOM-структурой, поэтому:

  • компоненты отвечают за привязку к DOM
  • анимации — только за визуальное поведение

Повторно используемые шаблоны (presets)

Пресеты позволяют стандартизировать анимации:

// /presets/burstPreset.js
export const BURST_PRESET = {
  radius: { 0: 100 },
  count: 12,
  children: {
    shape: 'circle',
    fill: 'white'
  }
};

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

createBurst(BURST_PRESET);

Управление зависимостями

  • минимизация прямых импортов
  • использование промежуточных слоёв
  • избегание циклических зависимостей

Интеграция с сборщиками

Модульная структура хорошо сочетается с:

  • Webpack
  • Vite
  • Rollup

Позволяет:

  • разделять код (code splitting)
  • оптимизировать загрузку
  • удалять неиспользуемый код (tree shaking)

Тестируемость модулей

Изолированные модули проще тестировать:

import { createBurst } from './burst.js';

test('creates burst animation', () => {
  const burst = createBurst();
  expect(burst).toBeDefined();
});

Повторное использование между проектами

Модульная архитектура позволяет переносить:

  • отдельные анимации
  • библиотеки эффектов
  • наборы пресетов

в другие проекты без изменений.


Организация больших анимационных систем

При создании сложных интерфейсов:

  • каждая анимация — отдельный модуль
  • каждая группа анимаций — компонент
  • глобальные параметры — в пресетах

Такой подход обеспечивает:

  • читаемость
  • гибкость
  • контроль над сложностью