Привет! Хочу рассказать о своей библиотеке hq-cropper — инструменте для обрезки изображений на чистом TypeScript без единой зависимости.
Когда искал cropper для своего проекта, столкнулся с двумя проблемами. Во-первых, большинство популярных решений тянут за собой кучу зависимостей и весят 100+ KB. Во-вторых, мало кто работает с большими изображениями.
Решил написать своё решение: лёгкое, без зависимостей, с умным алгоритмом масштабирования.
Проблема больших изображений
Ситуация: пользователь загружает фото 4000×3000 пикселей, а вам нужен аватар 200×200. Большинство кропперов справляются с этим плохо:
-
Наивный подход: обрезать в полном разрешении, потом уменьшить → жрёт память, тормозит
-
Простое уменьшение: сначала downscale, потом crop → теряем качество
-
Фиксированный размер: всегда одинаковые dimensions на выходе → нет гибкости
Главная сложность — найти баланс: нужны маленькие файлы на выходе, но нельзя убивать качество когда исходник и так небольшой.
Как решает hq-cropper
Библиотека использует логарифмический алгоритм масштабирования, управляемый параметром quality:
-
Маленькие исходники → минимальное или нулевое уменьшение (сохраняем качество)
-
Большие исходники → пропорциональное уменьшение (снижаем размер файла)
Параметр quality (по умолчанию 1.01) — это основание логарифма для расчёта выходных размеров:
outputSize = log(cropSelectionSize) / log(quality)
-
quality: 1.01→ большой выход (почти 1:1 с выделением) -
quality: 1.5→ средний выход (хороший баланс) -
quality: 2.0→ маленький выход (агрессивное сжатие)
Практические примеры
Аватары (баланс качества и размера)
const cropper = HqCropper(onSubmit, {
quality: 1.5,
compression: 0.85,
type: 'jpeg',
})
Результат: выделение 500px → ~180px на выходе. Выделение 200px → ~150px. Маленькие выделения остаются чёткими, большие разумно сжимаются.
Превью для галереи (минимальный размер)
const cropper = HqCropper(onSubmit, {
quality: 2.0,
compression: 0.7,
type: 'jpeg',
})
Результат: агрессивное уменьшение. 500px → ~130px. Идеально для превью где важен размер файла.
Высокое качество (сохранить детали)
const cropper = HqCropper(onSubmit, {
quality: 1.01,
compression: 1,
type: 'png',
})
Результат: почти 1:1. 500px → ~490px. Максимальное качество, большие файлы.
|
Исходник |
Выделение |
quality: 1.01 |
quality: 1.5 |
quality: 2.0 |
|
4000×3000 |
800px |
~780px |
~210px |
~130px |
|
1200×800 |
400px |
~390px |
~170px |
~120px |
|
400×400 |
200px |
~195px |
~150px |
~110px |
Обратите внимание: маленькие выделения сохраняют больше относительного размера — это защищает качество когда пользователь работает с небольшими изображениями.
Дополнительные настройки
const cropper = HqCropper(onSubmit, {
// Логарифмический коэффициент
quality: 1.5,
// JPEG компрессия (0-1, где 1 — лучшее качество)
compression: 0.85,
// Формат
type: 'jpeg', // или 'png'
})
Комбинации:
-
quality: 1.5+compression: 0.85→ Баланс (рекомендую для аватаров) -
quality: 2.0+compression: 0.7→ Минимальные файлы -
quality: 1.01+compression: 1+type: 'png'→ Максимальное качество
Возможности
-
Ноль зависимостей — чистый TypeScript, ~22KB minified
-
Framework agnostic — работает с любым стеком
-
Умное масштабирование — логарифмический алгоритм
-
Drag & resize — интуитивный UI с угловыми хэндлами
-
Валидация файлов — проверка типа и размера
-
Обработка ошибок — callback-based error reporting
-
Полная типизация — TypeScript из коробки
Быстрый старт
npm install hq-cropper
import { HqCropper } from 'hq-cropper'
const cropper = HqCropper((base64, blob, state) => {
document.querySelector('img').src = base64
console.log(`Обрезано ${state.fileName}: ${blob?.size} байт`)
})
document.querySelector('button').addEventListener('click', () => {
cropper.open()
})
Пример для React
import { useRef, useState } from 'react'
import { HqCropper } from 'hq-cropper'
function AvatarUpload() {
const [avatar, setAvatar] = useState('')
const cropperRef = useRef(
HqCropper(
(base64) => setAvatar(base64),
{
portalSize: 200,
quality: 1.5,
compression: 0.85,
},
undefined,
(error) => console.error(error)
)
)
return (
<div>
{avatar && <img src={avatar} alt="Avatar" />}
<button onClick={() => cropperRef.current.open()}>
Загрузить аватар
</button>
</div>
)
}
Все параметры конфигурации
const cropper = HqCropper(
onSubmit,
{
// Настройки портала (область выделения)
portalSize: 150,
minPortalSize: 50,
portalPosition: 'center',
// Настройки выхода
type: 'jpeg',
quality: 1.5,
compression: 0.85,
// Валидация
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedTypes: ['image/jpeg', 'image/png'],
// Локализация
applyButtonLabel: 'Применить',
cancelButtonLabel: 'Отмена',
},
// Кастомные css стили
{
root: ['my-cropper-modal'],
portal: ['my-crop-area'],
applyButton: ['btn', 'btn-primary'],
cancelButton: ['btn', 'btn-outline'],
},
(error) => alert(error)
)
Что нового в v3.2.0
Библиотека существует уже несколько лет и используется в продакшене в ряде проектов. Но до этого релиза накопился технический долг: утечки памяти, баги в resize логике, отсутствие валидации. В версии 3.2.0 провёл основательный рефакторинг и закрыл все известные проблемы.
Исправления:
-
Утечки памяти (корректная очистка при закрытии)
-
Race conditions в canvas операциях
-
Resize хэндлы во всех углах
Новые возможности:
-
onErrorcallback для обработки ошибок -
maxFileSizeиallowedTypesдля валидации файлов -
minPortalSizeпротив слишком маленьких выделений
Производительность:
-
Кэширование DOM элементов
-
requestAnimationFramethrottling для плавного drag
Кастомизация стилей
Библиотека позволяет полностью переопределить CSS-классы для любого элемента. Передайте свои классы через третий параметр:
const cropper = HqCropper(
onSubmit,
config,
{
root: ['my-cropper-modal'],
portal: ['my-crop-area'],
applyButton: ['btn', 'btn-primary'],
cancelButton: ['btn', 'btn-outline'],
}
)
Доступные элементы для кастомизации: root, header, body, footer, portal, portalArea, sourceImage, preview, previewImage, applyButton, cancelButton, и хэндлы для resize (handlerResizeTopLeft, handlerResizeTopRight, handlerResizeBottomLeft, handlerResizeBottomRight).
Это удобно когда нужно вписать cropper в существующую дизайн-систему или использовать свой CSS-фреймворк.
Ссылки
Это моя первая статья на habr, буду рад звёздочкам на GitHub и вопросам в комментариях!
Автор: isalikov
