hq-cropper: Image Cropper без зависимостей для JavaScript

в 11:45, , рубрики: canvas, image cropper, javascript, npm, open source, React, TypeScript, библиотека, обрезка изображений

Привет! Хочу рассказать о своей библиотеке 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 хэндлы во всех углах

Новые возможности:

  • onError callback для обработки ошибок

  • maxFileSize и allowedTypes для валидации файлов

  • minPortalSize против слишком маленьких выделений

Производительность:

  • Кэширование DOM элементов

  • requestAnimationFrame throttling для плавного 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js