Skip to content

Repository files navigation

Мульти-Пульти

Веб-редактор для рисования, создания покадровой анимации и экспорта коротких мультиков прямо в браузере.

О проекте · Возможности · Быстрый старт · Архитектура · Деплой

Баннер проекта Мульти-Пульти

О проекте

Мульти-Пульти - это браузерная творческая студия для детского рисования и простой покадровой анимации. Пользователь рисует на HTML5 Canvas, добавляет кадры на таймлайн, проигрывает результат как мультик и сохраняет работу в PNG или GIF.

Приложение не требует серверной части для основной работы. Все пользовательское состояние хранится локально в браузере через localStorage, а графика обрабатывается на клиенте. Это упрощает запуск, деплой и демонстрацию проекта: достаточно собрать статический сайт и отдать папку dist.

Интерфейс построен вокруг одного рабочего экрана:

  • сверху - основное меню с логотипом, отменой/повтором, загрузкой изображения, очисткой и экспортом;
  • слева - основные инструменты рисования;
  • рядом - контекстная панель настроек выбранного инструмента;
  • в центре - холст и временный overlay-слой для предпросмотра действий;
  • снизу - таймлайн кадров и управление воспроизведением.

Возможности

Рисование

  • Кисть с несколькими размерами.
  • Ластик, который работает как рисование белым цветом.
  • Заливка области выбранным цветом.
  • Пипетка для выбора цвета с холста.
  • Симметричное рисование по горизонтали.
  • Умный контур: свободный штрих может быть преобразован в ровную линию или эллипс.

Цвета

  • Базовая палитра цветов.
  • Недавние цвета, которые обновляются при выборе.
  • Избранные цвета.
  • Кастомный выбор цвета через hue-слайдер и поле насыщенности/яркости.
  • Перекраска выбранного объекта через режим выделения.

Объекты на холсте

  • Линия, круг и прямоугольник.
  • Добавление текста с выбором шрифта.
  • Редактирование ранее добавленного текста, если он распознан как выделенный объект.
  • Стикеры из встроенных категорий.
  • Перемещение, масштабирование, отражение и удаление выделенных элементов.

Анимация

  • Создание нескольких кадров.
  • Копирование текущего кадра.
  • Удаление кадра, если в проекте больше одного кадра.
  • Перетаскивание кадров на таймлайне.
  • Воспроизведение анимации с разной скоростью.
  • Экспорт всей последовательности кадров в GIF.

Сохранение и импорт

  • Экспорт текущего кадра в PNG.
  • Экспорт анимации в GIF.
  • Загрузка изображения на текущий кадр.
  • Автосохранение истории, текущего кадра, недавних и избранных цветов в localStorage.

Проверка содержимого

В проекте есть базовый фильтр содержимого кадра. Он не использует сервер и не отправляет рисунки наружу. Фильтр анализирует пиксели текущего canvas и оценивает несколько грубых признаков:

  • агрессивную палитру, где почти весь рисунок состоит из черного и красного;
  • длинные пересекающиеся линии и X-образные фигуры;
  • плотное хаотичное зачеркивание по всему холсту;
  • сильную четырехкратную симметрию как дополнительный риск-сигнал.

Фильтр эвристический: он нужен как мягкая защита от явно нежелательных рисунков, а не как полноценная система модерации. При блокировке кадр очищается или удаляется, а история переписывается, чтобы действие отмены не вернуло заблокированное содержимое.

Технологии

  • React 19
  • TypeScript
  • Vite 6
  • Tailwind CSS 4
  • HTML5 Canvas
  • gifenc
  • lucide-react
  • clsx
  • tailwind-merge

Быстрый старт

Требования

  • Node.js 20 или новее
  • npm

Установка

npm install

Запуск dev-сервера

npm run dev

По умолчанию Vite запускается на порту 3000:

http://localhost:3000

Скрипт запуска использует --host=0.0.0.0, поэтому приложение также может быть доступно по локальному сетевому адресу машины.

Скрипты

npm run dev

Запускает Vite dev-сервер на 3000 порту.

npm run lint

Запускает проверку TypeScript без генерации файлов: tsc --noEmit.

npm run build

Собирает production-версию приложения в папку dist.

npm run preview

Запускает Vite preview для проверки уже собранной версии.

npm run clean

Удаляет папку dist. В текущем виде команда использует rm -rf, поэтому в Windows ее удобнее запускать из Git Bash, WSL или другой оболочки, где доступна команда rm.

Архитектура

.
├── assets
│   ├── MultiPulit-Logo.png
│   └── multi-pulti-banner.png
├── public
│   └── MultiPulit-Logo.png
├── src
│   ├── App.tsx
│   ├── index.css
│   ├── main.tsx
│   └── utils
│       ├── audio.ts
│       ├── cn.ts
│       ├── contentFilter.ts
│       ├── extractObject.ts
│       ├── floodFill.ts
│       ├── gifExport.ts
│       └── shapeDetection.ts
├── index.html
├── package.json
├── render.yaml
├── tsconfig.json
└── vite.config.ts

src/App.tsx

Главный экран приложения. Здесь находятся:

  • состояние истории кадров;
  • текущий инструмент;
  • работа с цветами;
  • обработчики pointer-событий canvas;
  • логика стикеров, текста и выделения;
  • таймлайн кадров;
  • экспорт PNG/GIF;
  • запуск проверки содержимого кадра.

Canvas разделен на два слоя:

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

src/utils/floodFill.ts

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

src/utils/extractObject.ts

Извлечение объекта с холста в режиме выделения. Утилита ищет ближайший нарисованный пиксель, объединяет близкие компоненты и переносит найденный объект на отдельный canvas. Исходное место на основном canvas очищается.

src/utils/shapeDetection.ts

Распознавание простых фигур из свободного штриха. Сейчас поддерживаются:

  • линия;
  • эллипс.

Алгоритм смотрит на длину пути, размер bounding box и расстояние между началом и концом штриха.

src/utils/gifExport.ts

Экспорт последовательности кадров в GIF через gifenc. Каждый кадр рисуется на временный canvas, квантуется в палитру и записывается в GIF с задержкой, рассчитанной из FPS.

src/utils/contentFilter.ts

Пиксельный фильтр содержимого. Он вызывается после действий, которые меняют кадр: рисования, заливки и создания фигур. Если фильтр считает кадр нежелательным, приложение удаляет или очищает его и показывает мягкое предупреждение.

src/utils/audio.ts

Короткие звуки интерфейса через Web Audio API:

  • подтверждение;
  • действие;
  • удаление;
  • ошибка.

src/index.css

Глобальные стили и Tailwind-утилиты. Здесь определена основная кнопка btn-kid, активное состояние кнопок, стили color slider и запрет мобильных browser-жестов во время рисования.

Ассеты и логотип

В проекте используются два места для изображений:

  • assets - изображения, которые импортируются из кода или используются в README;
  • public - статические файлы, которые должны быть доступны из корня сайта.

Логотип лежит в двух местах:

  • assets/MultiPulit-Logo.png - используется внутри React-приложения в верхнем header;
  • public/MultiPulit-Logo.png - используется как favicon и apple-touch-icon в index.html.

Если нужно заменить логотип, замените оба файла или обновите пути в App.tsx и index.html.

Как работает состояние

Основное состояние проекта хранится в массиве истории:

{
  frames: string[];
}

Каждый кадр хранится как PNG dataURL. Это удобно для простого клиентского приложения: кадр легко отрисовать обратно на canvas, сохранить в localStorage и передать в GIF-экспортер.

В localStorage сохраняются:

  • история кадров;
  • текущая позиция в истории;
  • текущий кадр;
  • избранные цвета;
  • недавние цвета.

Если полная история становится слишком большой для localStorage, приложение пытается сохранить минимальное состояние с текущим кадром.

Основные пользовательские сценарии

Нарисовать кадр

  1. Выберите кисть.
  2. Выберите толщину и цвет.
  3. Нарисуйте на холсте.
  4. При отпускании указателя кадр сохраняется в историю.

Сделать мультик

  1. Нарисуйте первый кадр.
  2. Нажмите кнопку добавления кадра.
  3. Нарисуйте следующий кадр.
  4. Повторите несколько раз.
  5. Нажмите воспроизведение на таймлайне.
  6. При необходимости выберите скорость.

Экспортировать результат

  • PNG сохраняет только текущий кадр.
  • GIF сохраняет всю последовательность кадров.
  • Для GIF нужен минимум два кадра.

Отредактировать объект

  1. Выберите инструмент перемещения.
  2. Нажмите на нарисованный объект.
  3. Переместите, масштабируйте, отразите или перекрасьте выделение.
  4. При смене инструмента выделение финализируется на основном canvas.

Сборка

Проверка типов:

npm run lint

Production-сборка:

npm run build

После сборки результат появляется в папке:

dist

Эту папку можно отдать любым статическим сервером.

Деплой

В проекте есть конфигурация для Render:

services:
  - type: web
    name: multi-pulti
    env: static
    buildCommand: npm install && npm run build
    staticPublishPath: ./dist
    routes:
      - type: rewrite
        source: /*
        destination: /index.html

Для другого статического хостинга обычно достаточно:

  1. Запустить npm install.
  2. Запустить npm run build.
  3. Опубликовать содержимое папки dist.
  4. Настроить fallback на index.html, если хостинг требует явной настройки SPA-роутинга.

Проверка перед релизом

Минимальный чеклист:

  • npm run lint
  • npm run build
  • открыть приложение в браузере;
  • проверить рисование кистью;
  • проверить заливку;
  • добавить, скопировать и удалить кадр;
  • включить воспроизведение;
  • добавить текст;
  • добавить стикер;
  • загрузить изображение;
  • сохранить PNG;
  • создать минимум два кадра и сохранить GIF.

Диагностика

Порт 3000 занят

Остановите процесс, который уже использует порт, или временно измените порт в скрипте dev в package.json.

Не обновляется favicon

Браузеры агрессивно кешируют favicon. После замены public/MultiPulit-Logo.png попробуйте:

  • жестко обновить страницу;
  • открыть сайт в приватном окне;
  • очистить кеш сайта;
  • временно добавить версию к пути favicon в index.html, например /MultiPulit-Logo.png?v=2.

Пропал прогресс

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

GIF не сохраняется

Проверьте, что в проекте больше одного кадра. Экспорт GIF намеренно не запускается для одного кадра.

Сборка падает на запуске esbuild

В некоторых ограниченных окружениях Vite может не получить право запустить дочерний процесс esbuild. В обычной локальной среде это решается запуском команды без sandbox-ограничений.

Лицензия

Проект распространяется под лицензией MIT. Подробности указаны в файле LICENSE.

About

Веб-редактор для рисования и создания покадровой анимации в браузере на React, TypeScript и Canvas.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages