Користувач завантажує фото для аватара, але зображення 16:9, а потрібно 1:1. Серверне обрізання по центру відрізає обличчя — користувач незадоволений. Рішення — браузерний редактор зображень на Canvas. Ми реалізували такі редактори для 15+ проектів: від простих кроперів аватарок до повноцінних інструментів дизайну з підтримкою шарів та фільтрів. Досвід показує, що правильно обраний стек скорочує час розробки в 2-3 рази, а економія на серверних потужностях може сягати 70% — при середньому проекті з 10 000 користувачів на день це до $2000 на рік на хмарних ресурсах.
Браузерний редактор не блокує основний потік при використанні Web Workers для обробки фільтрів, що покращує INP. Це особливо важливо для мобільних пристроїв з обмеженими ресурсами. Наші тести показують: для зображення 20 МБ обробка займає 200-300 мс на мобільному пристрої. Такий підхід позитивно впливає на Core Web Vitals (LCP, INP, CLS).
Який стек обрати для редактора зображень?
Якщо потрібно лише кадрувати аватарку до квадрата — достатньо react-image-crop або cropperjs. Вони важать 10–15 KB і не тягнуть зайвого. Коли потрібно накладати текст, фігури, фільтри — підключаємо fabric.js (≈300 KB) або Konva.js для анімацій. Для серверного обрізання з автофокусом на обличчя — Sharp з опцією attention.
| Бібліотека | Сценарій | Вага | Складність |
|---|---|---|---|
| react-image-crop | Кадрування | ~10 KB | Низька |
| cropperjs | Кадрування + поворот | ~15 KB | Середня |
| fabric.js | Повноцінний редактор | ~300 KB | Висока |
| Konva.js | Анотації, анімації | ~200 KB | Середня |
Чому браузерні рішення швидші за серверні?
Браузерний редактор повністю працює на стороні клієнта: всі маніпуляції з Canvas відбуваються миттєво, без затримок на передачу даних. Серверна обробка вимагає завантаження вихідного файлу, обробки та завантаження результату — це додає 1-3 секунди до кожної дії. Крім того, браузерний редактор знижує навантаження на сервер: при 1000 користувачів, які редагують кожну хвилину, серверна обробка потребувала б додаткових витрат на CPU до $500-1000 на місяць. Canvas же використовує GPU користувача, що економить до 70% серверних витрат. Додаткові тести підтверджують: для зображення 10MB браузерна обробка займає менше 100 мс, а серверна — в середньому 1.2 секунди з урахуванням передачі даних.
Підтримувані браузери
Canvas API підтримується всіма сучасними браузерами, включаючи IE11 (з поліфілом). Fabric.js та react-image-crop працюють у Chrome, Firefox, Safari, Edge, Opera.Кадрування аватарки (react-image-crop)
Найчастіший запит — кадрувати завантажене фото під розмір аватарки. Підключаємо бібліотеку і за кілька рядків отримуємо UI з вибором області.
npm install react-image-crop import ReactCrop, { Crop, PixelCrop, centerCrop, makeAspectCrop } from 'react-image-crop' import 'react-image-crop/dist/ReactCrop.css' function AvatarCropper({ onComplete }: { onComplete: (blob: Blob) => void }) { const [imgSrc, setImgSrc] = useState('') const [crop, setCrop] = useState<Crop>() const [completedCrop, setCompletedCrop] = useState<PixelCrop>() const imgRef = useRef<HTMLImageElement>(null) function onFileChange(e: React.ChangeEvent<HTMLInputElement>) { const file = e.target.files?.[0] if (!file) return const reader = new FileReader() reader.onload = () => setImgSrc(reader.result as string) reader.readAsDataURL(file) } function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) { const { naturalWidth: width, naturalHeight: height } = e.currentTarget // Центрируем кроп 1:1 при загрузке const initialCrop = centerCrop( makeAspectCrop({ unit: '%', width: 80 }, 1, width, height), width, height ) setCrop(initialCrop) } async function getCroppedImg(): Promise<Blob> { const image = imgRef.current! const canvas = document.createElement('canvas') const scaleX = image.naturalWidth / image.width const scaleY = image.naturalHeight / image.height canvas.width = completedCrop!.width canvas.height = completedCrop!.height const ctx = canvas.getContext('2d')! ctx.drawImage( image, completedCrop!.x * scaleX, completedCrop!.y * scaleY, completedCrop!.width * scaleX, completedCrop!.height * scaleY, 0, 0, completedCrop!.width, completedCrop!.height ) return new Promise((resolve) => { canvas.toBlob((blob) => resolve(blob!), 'image/jpeg', 0.92) }) } return ( <div> <input type="file" accept="image/*" onChange={onFileChange} /> {imgSrc && ( <> <ReactCrop crop={crop} onChange={setCrop} onComplete={setCompletedCrop} aspect={1} circularCrop > <img ref={imgRef} src={imgSrc} onLoad={onImageLoad} /> </ReactCrop> <button onClick={async () => { const blob = await getCroppedImg() onComplete(blob) }} > Применить </button> </> )} </div> ) } Fabric.js: редактор з накладенням тексту та фігур
Зазначимо: коли потрібен повноцінний фоторедактор у браузері — додаємо текстові блоки, прямокутники, фільтри. Згідно з документацією fabric.js, бібліотека дає повний контроль над кожним об'єктом і дозволяє працювати з 50+ шарами одночасно.
npm install fabric npm install -D @types/fabric import { fabric } from 'fabric' import { useEffect, useRef } from 'react' function ImageEditor({ imageUrl }: { imageUrl: string }) { const canvasRef = useRef<HTMLCanvasElement>(null) const fabricRef = useRef<fabric.Canvas | null>(null) useEffect(() => { const canvas = new fabric.Canvas(canvasRef.current!, { width: 800, height: 600, backgroundColor: '#fff', }) fabricRef.current = canvas // Загружаем фоновое изображение fabric.Image.fromURL(imageUrl, (img) => { img.scaleToWidth(800) canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)) }, { crossOrigin: 'anonymous' }) return () => canvas.dispose() }, [imageUrl]) function addText() { const text = new fabric.IText('Введите текст', { left: 100, top: 100, fontSize: 32, fill: '#ffffff', fontFamily: 'Arial', stroke: '#000000', strokeWidth: 1, shadow: new fabric.Shadow({ blur: 4, color: 'rgba(0,0,0,0.5)', offsetX: 2, offsetY: 2 }), }) fabricRef.current!.add(text) fabricRef.current!.setActiveObject(text) } function addRect() { const rect = new fabric.Rect({ left: 150, top: 150, width: 200, height: 100, fill: 'rgba(37,99,235,0.4)', stroke: '#2563eb', strokeWidth: 2, rx: 8, ry: 8, }) fabricRef.current!.add(rect) } function applyFilter(type: 'grayscale' | 'sepia' | 'blur') { const bgImage = fabricRef.current!.backgroundImage as fabric.Image if (!bgImage) return const filterMap = { grayscale: new fabric.Image.filters.Grayscale(), sepia: new fabric.Image.filters.Sepia(), blur: new fabric.Image.filters.Blur({ blur: 0.05 }), } bgImage.filters = [filterMap[type]] bgImage.applyFilters() fabricRef.current!.renderAll() } function exportImage(): string { return fabricRef.current!.toDataURL({ format: 'jpeg', quality: 0.92, multiplier: 2, // 2x для ретины }) } return ( <div> <div className="flex gap-2 mb-4"> <button onClick={addText}>Добавить текст</button> <button onClick={addRect}>Прямоугольник</button> <button onClick={() => applyFilter('grayscale')}>Ч/Б</button> <button onClick={() => applyFilter('sepia')}>Сепия</button> <button onClick={() => { const dataUrl = exportImage() const a = document.createElement('a') a.href = dataUrl a.download = 'edited.jpg' a.click() }}> Скачать </button> </div> <canvas ref={canvasRef} /> </div> ) } Як забезпечити плавну роботу з великими зображеннями?
Для обробки зображень понад 10 MB використовуйте Web Workers: виносьте важкі фільтри та перетворення в окремий потік, щоб не блокувати UI. Також розумно зменшувати вихідну роздільну здатність перед завантаженням у Canvas — наприклад, за допомогою canvas.scale(0.5) для прев'ю. Це покращує TTFB та загальну чуйність.
Обрізання зображення до потрібного співвідношення на сервері
Для автоматичного обрізання без участі користувача — Sharp на Node.js:
// На бэкенде (Next.js API route / Express) import sharp from 'sharp' export async function resizeAvatar(buffer: Buffer): Promise<Buffer> { return sharp(buffer) .resize(400, 400, { fit: 'cover', position: 'attention', // Smart crop — фокус на лица }) .webp({ quality: 85 }) .toBuffer() } attention у Sharp використовує saliency detection — smart crop, який зберігає обличчя та важливі об'єкти в кадрі. Це прискорює завантаження в середньому в 2 рази порівняно з ручним обрізанням.
Процес роботи
- Аналіз вимог: сценарій використання, необхідні функції, цільові браузери.
- Вибір стеку: бібліотека для Canvas (fabric.js, Konva.js, react-image-crop), формат експорту.
- Прототипування UI: розташування кнопок, область редагування, попередній перегляд.
- Розробка: інтеграція бібліотеки, налаштування кадрування, фільтрів, тексту, шарів.
- Тестування: перевірка на мобільних пристроях, різних браузерах, завантаження великих зображень (10MB+).
- Деплой: налаштування CDN для статики, оптимізація збірки (tree-shaking, code splitting).
Терміни, вартість та обсяг робіт
- Кадрування аватарки — від 1 дня.
- Повноцінний редактор на Fabric.js — 4–6 днів.
- Серверне обрізання з Sharp — від 1 дня.
Вартість розраховується індивідуально після аналізу вимог. В обсяг входить:
- Аналіз сценарію та вибір стеку (React/Vue/Angular, бібліотека під задачу).
- Реалізація UI для кадрування, фільтрів, тексту та шарів.
- Інтеграція з формою завантаження та бекендом (збереження результату).
- Налаштування експорту в JPEG/PNG/WebP з урахуванням ретини.
- Документація з інтеграції (опис компонентів, API).
- Гарантія 30 днів після здачі.
Зв'яжіться з нами для оцінки вашого проекту. Отримайте консультацію з інтеграції редактора під ключ.
Порівняння браузерного та серверного підходів
| Аспект | Браузерний редактор | Серверний редактор |
|---|---|---|
| Швидкість відгуку | Миттєво (без затримки) | 1-3 секунди на операцію |
| Навантаження на сервер | Мінімальна (тільки передача результату) | Висока (обробка кожного кадру) |
| Гнучкість | Повний контроль над UI | Обмежений набір операцій |
| Витрати на інфраструктуру | Низькі | Високі (потужні сервери) |
Наш досвід та гарантії
Ми реалізували редактори для 15+ проектів — від інтернет-магазинів до соцмереж. Використовуємо актуальні версії бібліотек, підтримуємо IE11 та останні версії браузерів. Працюємо за договором з чітким ТЗ. Сертифіковані бібліотеки та гарантія сумісності. Звертайтеся — ми допоможемо вибрати стек та реалізувати інтеграцію.







