Обрізка та редагування зображень перед завантаженням: готове рішення

Клієнтська обрізка зображень: чому це важливо?

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Обрізка та редагування зображень перед завантаженням: готове рішення
Середній
~2-3 дні

Наші компетенції:

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1422
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    984
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    1000

Клієнтська обрізка зображень: чому це важливо?

Більшість користувачів завантажують фотографії прямо з телефону: величезний оригінал 4000×3000 та 8 МБ. Сервер має прийняти, обробити та зберегти — навантаження на I/O та CPU зростає. Клієнтська обрізка вирішує це до відправлення: вирізаємо потрібний фрагмент, повертаємо, коригуємо яскравість — на сервер іде вже оптимізований файл. Ми використовуємо перевірені бібліотеки Cropper.js та react-image-crop, щоб вписати кроп у будь-який інтерфейс. Навіщо ганяти мегабайти, якщо можна відправити тільки те, що потрібно? Досвід 7+ років та 50+ проєктів з обробкою зображень підтверджує: такий підхід знижує навантаження на сервер у 3–5 разів та прискорює завантаження сторінок. Крім того, клієнтська обробка дозволяє заощадити до 70% коштів на зберігання та скоротити витрати на трафік у 5 разів. А ще це позитивно впливає на Core Web Vitals — особливо LCP, адже браузеру не потрібно чекати серверної обробки.

Які проблеми вирішує кроп на клієнті?

  • Неправильні пропорції: користувачі завантажують горизонтальні фото для вертикальних аватарів — доводиться обрізати вручну.
  • Величезна вага: вихідник 10 МБ перетворюється на 200 KB після кропу в WebP 0.9.
  • EXIF-поворот: мобільні знімки відображаються перевернутими до виправлення метаданих.
  • Зайві елементи: рекламні банери, водяні знаки — все це видаляється до завантаження.
  • Завантаження на повільному з'єднанні: користувачі з 3G можуть чекати хвилини, а після кропу відправляється тільки 200 KB.

Як вибрати бібліотеку для кропу?

Бібліотека Розмір (gzip) Фічі Для яких задач
Cropper.js ~34 KB Поворот, зум, обрізка, аспект Vanilla JS, jQuery, будь-який стек
react-image-crop ~6 KB Обрізка з аспектом, кастомний UI React (18+, Next.js)
vue-cropper ~15 KB Обрізка, поворот, зум Vue 2/3, Nuxt
Fabric.js ~200 KB Повноцінний редактор, шари, фільтри, текст Якщо потрібен редактор з накладанням тексту/фільтрів

Cropper.js у 2 рази швидше завантажується, ніж Fabric.js, для простого кропу. Якщо задача — тільки кадрування з фіксованим аспектом, react-image-crop легший у 5 разів.

Базова реалізація на React

import { useState, useRef, useCallback } from 'react' import ReactCrop, { Crop, PixelCrop, centerCrop, makeAspectCrop } from 'react-image-crop' import 'react-image-crop/dist/ReactCrop.css' function centerAspectCrop(width: number, height: number, aspect: number): Crop { return centerCrop( makeAspectCrop({ unit: '%', width: 90 }, aspect, width, height), width, height ) } export function ImageCropUploader({ aspect = 1, onUpload }: { aspect?: number onUpload: (blob: Blob) => Promise<void> }) { const [imgSrc, setImgSrc] = useState('') const [crop, setCrop] = useState<Crop>() const [completedCrop, setCompletedCrop] = useState<PixelCrop>() const imgRef = useRef<HTMLImageElement>(null) function onSelectFile(e: React.ChangeEvent<HTMLInputElement>) { if (e.target.files?.[0]) { const reader = new FileReader() reader.addEventListener('load', () => setImgSrc(reader.result?.toString() ?? '')) reader.readAsDataURL(e.target.files[0]) } } function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) { const { width, height } = e.currentTarget setCrop(centerAspectCrop(width, height, aspect)) } const getCroppedBlob = useCallback((): Promise<Blob> => { const image = imgRef.current if (!image || !completedCrop) throw new Error('No crop data') const canvas = document.createElement('canvas') const scaleX = image.naturalWidth / image.width const scaleY = image.naturalHeight / image.height canvas.width = completedCrop.width * scaleX canvas.height = completedCrop.height * scaleY const ctx = canvas.getContext('2d')! ctx.drawImage( image, completedCrop.x * scaleX, completedCrop.y * scaleY, completedCrop.width * scaleX, completedCrop.height * scaleY, 0, 0, canvas.width, canvas.height ) return new Promise((resolve, reject) => { canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Canvas empty')), 'image/webp', 0.9) }) }, [completedCrop]) async function handleSubmit() { const blob = await getCroppedBlob() await onUpload(blob) } return ( <div> <input type="file" accept="image/*" onChange={onSelectFile} /> {imgSrc && ( <ReactCrop crop={crop} onChange={setCrop} onComplete={setCompletedCrop} aspect={aspect} minWidth={100} > <img ref={imgRef} src={imgSrc} onLoad={onImageLoad} /> </ReactCrop> )} {completedCrop && ( <button onClick={handleSubmit}>Завантажити</button> )} </div> ) } 

Ключовий момент — масштабування через naturalWidth / width. Браузер відображає зображення в зменшеному вигляді, а вирізати потрібно з оригіналу.

Як реалізувати кроп за 5 кроків?

  1. Вибір бібліотеки залежно від фреймворку (таблиця вище).
  2. Завантаження файлу через FileReader та відображення в <img>.
  3. Ініціалізація кроп-інтерфейсу з параметрами аспекту та мінімальним розміром.
  4. Отримання обрізаного фрагменту через canvas.toBlob() у формат WebP з якістю 0.9.
  5. Відправлення Blob через FormData на сервер.

Додатково: валідація розміру до кропу (обмеження 10 МБ), перевірка EXIF-орієнтації та попередній перегляд результату.

Cropper.js: поворот, зум та завантаження

<div> <img id="image" src="/original.jpg"> <button onclick="cropper.rotate(90)">Повернути</button> <button onclick="cropper.zoom(0.1)">+</button> <button onclick="cropper.zoom(-0.1)">-</button> <button onclick="uploadCropped()">Завантажити</button> </div> <script> const cropper = new Cropper(document.getElementById('image'), { aspectRatio: 16 / 9, viewMode: 2, // не виходити за межі зображення dragMode: 'move', autoCropArea: 0.8, restore: false, guides: true, center: true, highlight: false, cropBoxMovable: true, cropBoxResizable: true, toggleDragModeOnDblclick: false, }) async function uploadCropped() { cropper.getCroppedCanvas({ maxWidth: 1920, maxHeight: 1080, imageSmoothingEnabled: true, imageSmoothingQuality: 'high', }).toBlob(async (blob) => { const fd = new FormData() fd.append('image', blob, 'cover.webp') await fetch('/api/upload', { method: 'POST', body: fd }) }, 'image/webp', 0.85) } </script> 

Що робити з EXIF-орієнтацією?

Мобільні фото часто повернуті через метадані EXIF. Браузер до недавнього часу не враховував це автоматично. Ось як це виправити:

img { image-orientation: from-image; /* підтримується в сучасних браузерах */ } 

Для надійності використовуємо бібліотеку exifr:

import { parse } from 'exifr' async function fixOrientation(file: File): Promise<string> { const exif = await parse(file, { orientation: true }) return new Promise(resolve => { const reader = new FileReader() reader.onload = (e) => { const img = new Image() img.onload = () => { const canvas = document.createElement('canvas') const rotation = exif?.Orientation ?? 1 // застосовуємо трансформацію за таблицею EXIF applyExifRotation(canvas, img, rotation) resolve(canvas.toDataURL()) } img.src = e.target!.result as string } reader.readAsDataURL(file) }) } 

Докладніше про властивість image-orientation читайте в MDN.

Валідація та обмеження

Завантажувати 20 МБ оригінал, щоб потім вирізати 200×200 — марнотратство. Перевіряємо перед показом кропу:

const MAX_SIZE_MB = 10 const MAX_DIMENSION = 4096 function validateImage(file: File): string | null { if (file.size > MAX_SIZE_MB * 1024 * 1024) { return `Файл занадто великий. Максимум ${MAX_SIZE_MB} МБ` } return null } // Для перевірки розмірів — тільки після завантаження в img function checkDimensions(img: HTMLImageElement): string | null { if (img.naturalWidth > MAX_DIMENSION || img.naturalHeight > MAX_DIMENSION) { return `Зображення занадто велике. Максимум ${MAX_DIMENSION}px` } return null } 

Якщо потрібно прийняти великі файли — ресайзити до розумних розмірів перед кропом через canvas.drawImage зі зменшеними canvas.width/height.

Що входить в роботу

Компонент Опис
Вибір бібліотеки Підбір оптимального рішення під ваш стек (React, Vue, vanilla)
Інтеграція кропу Налаштування компонента з підтримкою аспекту, повороту, зуму
EXIF-корекція Виправлення орієнтації мобільних знімків
Валідація Перевірка розміру, розширення та типу файлу до обробки
Оптимізація Стиснення в WebP з контрольованою якістю
Попередній перегляд Живий перегляд результату до завантаження
Документація Інструкція з інтеграції та підтримки

Довірити це завдання професіоналам

Наш досвід: 7+ років у веб-розробці, 50+ проєктів з обробкою зображень на клієнті. Ми гарантуємо, що інтеграція буде виконана вчасно, код буде покритий тестами та документований. Отримайте консультацію нашого інженера — це безкоштовно та займе 30 хвилин. Замовте впровадження клієнтського кропу та скоротіть витрати на серверну інфраструктуру.

Підсумок: клієнтська обрізка знижує навантаження на сервер, покращує UX та економить трафік. Впровадьте її за 2–5 днів з нашою допомогою.