Веб-редактор для рисования, создания покадровой анимации и экспорта коротких мультиков прямо в браузере.
О проекте · Возможности · Быстрый старт · Архитектура · Деплой
Мульти-Пульти - это браузерная творческая студия для детского рисования и простой покадровой анимации. Пользователь рисует на 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 installnpm 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
Главный экран приложения. Здесь находятся:
- состояние истории кадров;
- текущий инструмент;
- работа с цветами;
- обработчики pointer-событий canvas;
- логика стикеров, текста и выделения;
- таймлайн кадров;
- экспорт PNG/GIF;
- запуск проверки содержимого кадра.
Canvas разделен на два слоя:
- основной canvas хранит фактический кадр;
- overlay canvas используется для предпросмотра кисти, фигур, текста, стикеров и выделений до финального сохранения.
Заливка области цветом. Используется построчный алгоритм, чтобы не переполнять стек на больших областях и быстрее обходить соседние пиксели. Цвет сравнивается с допуском, потому что на краях линий есть сглаживание.
Извлечение объекта с холста в режиме выделения. Утилита ищет ближайший нарисованный пиксель, объединяет близкие компоненты и переносит найденный объект на отдельный canvas. Исходное место на основном canvas очищается.
Распознавание простых фигур из свободного штриха. Сейчас поддерживаются:
- линия;
- эллипс.
Алгоритм смотрит на длину пути, размер bounding box и расстояние между началом и концом штриха.
Экспорт последовательности кадров в GIF через gifenc. Каждый кадр рисуется на временный canvas, квантуется в палитру и записывается в GIF с задержкой, рассчитанной из FPS.
Пиксельный фильтр содержимого. Он вызывается после действий, которые меняют кадр: рисования, заливки и создания фигур. Если фильтр считает кадр нежелательным, приложение удаляет или очищает его и показывает мягкое предупреждение.
Короткие звуки интерфейса через Web Audio API:
- подтверждение;
- действие;
- удаление;
- ошибка.
Глобальные стили и 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, приложение пытается сохранить минимальное состояние с текущим кадром.
- Выберите кисть.
- Выберите толщину и цвет.
- Нарисуйте на холсте.
- При отпускании указателя кадр сохраняется в историю.
- Нарисуйте первый кадр.
- Нажмите кнопку добавления кадра.
- Нарисуйте следующий кадр.
- Повторите несколько раз.
- Нажмите воспроизведение на таймлайне.
- При необходимости выберите скорость.
- PNG сохраняет только текущий кадр.
- GIF сохраняет всю последовательность кадров.
- Для GIF нужен минимум два кадра.
- Выберите инструмент перемещения.
- Нажмите на нарисованный объект.
- Переместите, масштабируйте, отразите или перекрасьте выделение.
- При смене инструмента выделение финализируется на основном canvas.
Проверка типов:
npm run lintProduction-сборка:
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Для другого статического хостинга обычно достаточно:
- Запустить
npm install. - Запустить
npm run build. - Опубликовать содержимое папки
dist. - Настроить fallback на
index.html, если хостинг требует явной настройки SPA-роутинга.
Минимальный чеклист:
npm run lintnpm run build- открыть приложение в браузере;
- проверить рисование кистью;
- проверить заливку;
- добавить, скопировать и удалить кадр;
- включить воспроизведение;
- добавить текст;
- добавить стикер;
- загрузить изображение;
- сохранить PNG;
- создать минимум два кадра и сохранить GIF.
Остановите процесс, который уже использует порт, или временно измените порт в скрипте dev в package.json.
Браузеры агрессивно кешируют favicon. После замены public/MultiPulit-Logo.png попробуйте:
- жестко обновить страницу;
- открыть сайт в приватном окне;
- очистить кеш сайта;
- временно добавить версию к пути favicon в
index.html, например/MultiPulit-Logo.png?v=2.
Прогресс хранится в localStorage конкретного браузера и домена. Он может пропасть после очистки данных сайта, запуска на другом домене или в другом браузере.
Проверьте, что в проекте больше одного кадра. Экспорт GIF намеренно не запускается для одного кадра.
В некоторых ограниченных окружениях Vite может не получить право запустить дочерний процесс esbuild. В обычной локальной среде это решается запуском команды без sandbox-ограничений.
Проект распространяется под лицензией MIT. Подробности указаны в файле LICENSE.
