hq-cropper: Zero-Dependency Image Cropper for JS
Have you ever needed a simple, lightweight image cropper for profile pictures or avatars? I've been working on hq-cropper — a zero-dependency TypeScript library that does exactly that.
The Problem with Large Images
Here's a common scenario: your user uploads a 4000×3000 pixel photo from their smartphone, but you only need a 200×200 avatar. Most croppers handle this poorly:
- Naive approach: Crop at full resolution, then resize → wastes memory, slow on mobile
- Simple resize: Downscale first, then crop → loses too much quality
- Fixed output size: Always outputs the same dimensions → no flexibility
The challenge is finding the right balance: you want small output files, but you don't want to destroy quality when the source image is already small.
How hq-cropper Solves This
hq-cropper uses a logarithmic scaling algorithm controlled by the quality parameter. Here's the key insight:
- Small source images → minimal or no downscaling (preserves quality)
- Large source images → proportional downscaling (reduces file size)
The quality parameter (default: 1.01) controls this behavior. It's the logarithm base used to calculate output dimensions from the crop selection size.
How It Works
outputSize = log(cropSelectionSize) / log(quality)
-
quality: 1.01→ Large output (almost 1:1 with selection) -
quality: 1.5→ Medium output (good balance) -
quality: 2.0→ Small output (aggressive compression)
Practical Examples
Avatar Upload (Balance Quality & Size)
For profile pictures where you want decent quality but reasonable file sizes:
const cropper = HqCropper(onSubmit, {
quality: 1.5,
compression: 0.85,
type: 'jpeg',
})
Result: A 500px crop selection produces ~180px output. A 200px selection produces ~150px output. Small selections stay sharp, large selections get reasonably compressed.
Thumbnail Generation (Smallest Possible)
When file size matters most (e.g., gallery thumbnails):
const cropper = HqCropper(onSubmit, {
quality: 2.0,
compression: 0.7,
type: 'jpeg',
})
Result: Aggressive downscaling. A 500px selection → ~130px output. Perfect for thumbnails where you need tiny files.
High-Quality Crop (Preserve Details)
When quality is paramount (e.g., portfolio images):
const cropper = HqCropper(onSubmit, {
quality: 1.01,
compression: 1,
type: 'png',
})
Result: Nearly 1:1 output. A 500px selection → ~490px output. Maximum quality, larger files.
Real-World Comparison
| Source Image | Crop Selection | 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 |
Notice how smaller source selections maintain more relative size — this preserves quality when users are already working with smaller images.
Additional Output Controls
Beyond quality, you have fine-grained control:
const cropper = HqCropper(onSubmit, {
// Logarithmic scaling factor
quality: 1.5,
// JPEG compression (0-1, where 1 is best)
compression: 0.85,
// Output format
type: 'jpeg', // or 'png'
})
Compression is standard JPEG quality (0.0 - 1.0). Combined with quality, you get precise control:
-
quality: 1.5+compression: 0.85→ Balanced (recommended for avatars) -
quality: 2.0+compression: 0.7→ Smallest files -
quality: 1.01+compression: 1+type: 'png'→ Maximum quality
Why Another Image Cropper?
Most existing solutions are either:
- Tied to a specific framework (React, Vue, etc.)
- Bloated with dependencies
- Overcomplicated for simple use cases
- Don't handle the large-to-small image problem well
I wanted something that:
- Works everywhere (vanilla JS, React, Vue, Angular)
- Has zero dependencies
- Focuses on square crops (perfect for avatars)
- Intelligently handles any source image size
Features
- Zero dependencies — pure TypeScript, ~22KB minified
- Framework agnostic — works with any stack
- Smart scaling — logarithmic algorithm for optimal output
- Drag & resize — intuitive UI with corner handles
- File validation — built-in type and size checks
- Error handling — callback-based error reporting
- Fully typed — complete TypeScript support
Quick Start
npm install hq-cropper
import { HqCropper } from 'hq-cropper'
const cropper = HqCropper((base64, blob, state) => {
document.querySelector('img').src = base64
console.log(`Cropped ${state.fileName}: ${blob?.size} bytes`)
})
document.querySelector('button').addEventListener('click', () => {
cropper.open()
})
React Example
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()}>
Upload Avatar
</button>
</div>
)
}
All Configuration Options
const cropper = HqCropper(
onSubmit,
{
// Portal (crop area) settings
portalSize: 150,
minPortalSize: 50,
portalPosition: 'center',
// Output settings
type: 'jpeg',
quality: 1.5,
compression: 0.85,
// Validation
maxFileSize: 5 * 1024 * 1024,
allowedTypes: ['image/jpeg', 'image/png'],
// UI labels
applyButtonLabel: 'Save',
cancelButtonLabel: 'Cancel',
},
undefined,
(error) => alert(error)
)
What's New in v3.2.0
Bug Fixes:
- Fixed memory leaks (proper cleanup on modal close)
- Fixed race conditions in canvas operations
- Fixed resize handles in all corners
New Features:
-
onErrorcallback for graceful error handling -
maxFileSizeandallowedTypesfor file validation -
minPortalSizeto prevent tiny crop areas
Performance:
- DOM element caching
-
requestAnimationFramethrottling for smooth dragging
Links
If you find this useful, give it a ⭐ on GitHub! Questions? Drop a comment below.
Top comments (0)