@sargis-artashyan/react-smart-crop
v1.0.7
Published
Smart responsive image cropping using WebAssembly and computer vision. Faster than JS, privacy-focused, and client-side.
Downloads
1,013
Maintainers
Readme
react-smart-crop
Smart responsive image cropping for React powered by WebAssembly — 20-30x faster than JavaScript, privacy-focused, zero server latency
🎯 Live Demo & Preview
Experience the smart crop algorithm in action:
🔗 Live Demo ← Click to see it in action
✨ Why WebAssembly?
Traditional JavaScript image analysis is computationally expensive and slow. react-smart-crop uses C++ compiled to WebAssembly for:
Performance Advantages
- ⚡ 20-30x Faster — Near-native execution speed vs JavaScript
- 🔒 100% Client-Side — No server uploads, no privacy concerns
- 📉 Minimal Bandwidth — Only focal coordinates (2 integers), not pixel data
- 🚀 Zero Server Latency — Instant results in browser
- 📱 Mobile Optimized — Efficient resource usage on low-end devices
Smart Algorithm Features
- Edge Detection — Finds important contours and transitions
- Saliency Analysis — Scores regions by visual importance
- Color Distribution — Analyzes background and foreground
- Center-Bias Weighting — Biases toward natural composition
- Skin Tone Prioritized — Ideal for portrait photos and face-centric images
- Responsive Regions — 20×20 grid analysis for precision
📦 Installation
Requirements
- React 18.0.0 or higher (including React 19.x)
- Browser support: WebAssembly enabled
npm
npm install @sargis-artashyan/react-smart-cropyarn
yarn add @sargis-artashyan/react-smart-croppnpm
pnpm add @sargis-artashyan/react-smart-crop⚙️ Build Tool Configuration
Vite ✅ — Works out of the box! WASM is automatically handled.
Webpack 4/5 — Enable async WebAssembly support in webpack.config.js:
module.exports = {
experiments: {
asyncWebAssembly: true,
layers: true
}
};🚀 Quick Start
Basic Usage
import React from 'react';
import { SmartCropImage } from '@sargis-artashyan/react-smart-crop';
export function MyGallery() {
return (
<div style={{ width: '100%', height: '400px' }}>
<SmartCropImage
src="https://example.com/portrait.jpg"
// Width and height accept: '100%', '100px', '50vw', '100rem', or numbers (treated as px)
alt="Profile picture"
/>
</div>
);
}With Debug Mode
Enable debug mode to visualize the detected focal point:
<SmartCropImage
src="/images/photo.jpg"
width={300}
height={300}
debug={true} // Shows focal point indicator
alt="Product image"
/>Responsive Container
<div style={{
width: '100%',
maxWidth: '600px',
aspectRatio: '16 / 9',
margin: '0 auto'
}}>
<SmartCropImage
src="/images/banner.jpg"
width="100%"
height="100%"
alt="Banner"
/>
</div>Custom Styling with CSS
// Rounded avatar with shadow
<div style={{
width: '120px',
height: '120px',
borderRadius: '50%',
overflow: 'hidden',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
}}>
<SmartCropImage
src="user-profile.jpg"
width="100%"
height="100%"
className="avatar-image"
alt="User avatar"
/>
</div>� Advanced Usage
Multi-Point Focal Detection
Detect multiple focal points in a single image using Non-Maximum Suppression algorithm:
import { useSmartCrop, type FocalPoint } from '@sargis-artashyan/react-smart-crop';
export function MultiPointAnalysis() {
const { analyzeImage } = useSmartCrop();
const [points, setPoints] = useState<FocalPoint[]>([]);
const handleAnalyze = async (imageElement: HTMLImageElement) => {
// Request 5 focal points instead of default 1
const result = await analyzeImage(imageElement, 0, 5);
if (Array.isArray(result)) {
setPoints(result);
result.forEach((point, idx) => {
console.log(`Point ${idx + 1}: (${point.x.toFixed(1)}%, ${point.y.toFixed(1)}%) - Score: ${(point.score * 100).toFixed(0)}%`);
});
}
};
return (
<div>
<img id="img" src="complex-scene.jpg" alt="Scene with multiple subjects" />
<button onClick={() => handleAnalyze(document.getElementById('img'))}>
Find focal points
</button>
</div>
);
}How it works:
- Non-Maximum Suppression: After finding each focal point, the algorithm suppresses a region around it
- Score-Based Ranking: Each point receives a confidence score (0-1) based on saliency
- Adaptive Detection: Returns fewer points if image has less visual complexity
- Memory Safe: Automatically frees WASM heap memory
Use Cases:
- 📸 Multi-Subject Photos: Portrait groups, crowd scenes
- 🎬 Composition Analysis: Find all important regions for layout
- 🤖 AI Training Data: Generate focal point labels for ML models
- 🎨 Design Tools: Smart guides for optimal crop regions
Custom Hook Implementation
import { useSmartCrop } from '@sargis-artashyan/react-smart-crop';
import { useRef, useState } from 'react';
export function CustomCropAnalyzer() {
const imgRef = useRef<HTMLImageElement>(null);
const { analyzeImage, cancelAnalysis, updatePriority } = useSmartCrop();
const [result, setResult] = useState(null);
// Single focal point (default)
const analyzeSingle = async () => {
const result = await analyzeImage(imgRef.current, 0); // priority 0 = visible
setResult(result);
};
// Multiple focal points
const analyzeMulti = async (count: number) => {
const result = await analyzeImage(imgRef.current, 1, count); // priority 1 = preload
setResult(result);
};
// Change priority of queued task
const prioritizeCurrentTask = () => {
updatePriority(0); // Move to high priority queue
};
// Cancel ongoing analysis
const stopAnalysis = () => {
cancelAnalysis();
setResult(null);
};
return (
<>
<img ref={imgRef} src="photo.jpg" alt="Analysis target" />
<button onClick={analyzeSingle}>Analyze Single Point</button>
<button onClick={() => analyzeMulti(5)}>Analyze 5 Points</button>
<button onClick={prioritizeCurrentTask}>Prioritize</button>
<button onClick={stopAnalysis}>Cancel</button>
{result && (
<div>
{Array.isArray(result) ? (
<p>Found {result.length} focal points</p>
) : (
<p>Focal point: ({result.x}%, {result.y}%)</p>
)}
</div>
)}
</>
);
}TypeScript Types
// Single focal point (backward compatible)
interface SmartPoint {
x: number; // Percentage 0-100
y: number; // Percentage 0-100
}
// Multiple focal points (with confidence scores)
interface FocalPoint {
x: number; // Percentage 0-100
y: number; // Percentage 0-100
score: number; // Confidence 0-1 (higher = more important)
}
// Hook return type
type AnalysisResult = SmartPoint | FocalPoint[] | null;📚 API Reference
SmartCropImage Component Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| src | string | required | URL or path to the image to process. Must support CORS if cross-origin. |
| width | number \| string | '100%' | Width of the container: '100%', '50px', '50vw', '10rem', or 300 (treated as px) |
| height | number \| string | '100%' | Height of the container: '100px', '50vh', '10rem', or 300 (treated as px) |
| alt | string | '' | Alternative text for accessibility. Recommended. |
| debug | boolean | false | Show focal point visualization with pulsing indicator. Useful for testing. |
| className | string | '' | Custom CSS class applied to the container. For styling/theming. |
| enableBlurUp | boolean | true | Show blur-up placeholder during analysis (improves perceived performance). |
| placeholderColor | string | '#e2e8f0' | Background color of blur-up placeholder SVG. Any valid CSS color. |
Hook: useSmartCrop()
const { analyzeImage, cancelAnalysis, updatePriority, isReady } = useSmartCrop();
// Analyze single point (backward compatible)
const result: SmartPoint | null = await analyzeImage(source, priority);
// Analyze multiple points (new feature)
const results: FocalPoint[] | null = await analyzeImage(source, priority, maxPoints);Parameters:
source— HTMLImageElement, HTMLCanvasElement, or ImageBitmappriority— 0 (visible) | 1 (preload) | 2 (background, default)maxPoints— Number of focal points to find (1 = default, 2-10 recommended)
Methods:
cancelAnalysis()— Cancel current or queued taskupdatePriority(newPriority)— Upgrade priority of queued taskisReady— Whether WASM module is initialized
Component Behavior
- Lazy Loading: Analysis starts when component enters viewport (uses Intersection Observer)
- CORS Required: Remote images must have appropriate CORS headers
- Memory Safe: Automatically cleans up WASM memory after analysis
- Race Condition Safe: Discards results if image URL changes during analysis
- Error Handling: Graceful fallback with error message if image fails to load
- Debug Visualization: Shows all detected focal points with size scaled by confidence score
⚙️ Performance Comparison
| Task | JavaScript | WebAssembly | Speedup | |------|-----------|------------|----------| | Saliency Analysis (1 image) | ~450ms | ~15ms | 30x | | Grid Calculation | ~200ms | ~5ms | 40x | | Edge Detection | ~180ms | ~12ms | 15x |
Results on MacBook Pro M1, average of 100 iterations
�🔬 How It Works
Algorithm Overview
Image Downsampling (64×64 px)
- Reduces to standard size for consistent analysis
- Canvas API used for efficient resizing
Pixel Grid Analysis (20×20 regions)
- Divides image into 400 regions
- Each region scored for importance
Saliency Scoring
- Edge detection using gradient analysis
- Color distribution measurement
- Center-bias weighting (favors compositionally balanced points)
- Skin tone analysis for portrait photos
Max Pooling
- Identifies most important 3×3 region cluster
- Smooths out noise
Focal Point Calculation
- Converts region coordinates to pixel percentages
- Applied via CSS
object-positionfor responsive cropping
Data Flow Architecture
┌─────────────────────────────────────────────────────────┐
│ BROWSER (CLIENT) │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Image URL │────────>│ Intersection │ │
│ │ (CORS Safe) │ │ Observer │ │
│ └──────────────┘ │ (Lazy Load) │ │
│ └───────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Canvas 2D API │ │
│ │ (64×64 resize) │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ╔══════════════════════╗ │
│ ║ WASM MODULE ║ │
│ ║ (C++ Compiled) ║ │
│ ║ ║ │
│ ║ ✓ Edge Detection ║ │
│ ║ ✓ Saliency Analysis ║ │
│ ║ ✓ Max Pooling ║ │
│ ║ ✓ Focal Point Calc ║ │
│ ╚══════════════════════╝ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Result Object │ │
│ │ { x: 45, y: 60 } │ │
│ │ (percentages) │ │
│ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ CSS object-position Apply │ │
│ │ object-position: 45% 60% │ │
│ └──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Focused Image │ │
│ │ Rendered on Screen │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘Key Points:
- ✅ 100% Client-Side — No server communication
- ✅ Non-Destructive — Uses CSS, original image unmodified
- ✅ Memory Safe — Automatic cleanup after analysis
- ✅ Privacy — Image data never leaves your browser
Architecture
React Component (SmartCropImage)
↓
JavaScript Hook (useSmartCrop)
↓
WASM Module (C++ compiled)
↓
Canvas API (pixel data)
↓
Result: { x: %, y: % }Key Technologies:
- Emscripten: C++ to WASM compiler toolchain
- Canvas API: Pixel data extraction
- Intersection Observer: Lazy loading optimization
- CSS
object-position: Non-destructive cropping - WASM Memory: Manual malloc/free for performance
🐛 Troubleshooting & Caveats
CORS Issues
Problem: Image won't load or analysis fails with cross-origin images
Solution: Ensure the image server includes CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONSOn your own server (Node/Express example):
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});Pass crossOrigin="anonymous" if needed (handled automatically by component).
WASM Not Supported
Problem: Console error about WebAssembly not being available
Solution: Check browser support or add fallback:
import { SmartCropImage } from '@sargis-artashyan/react-smart-crop';
export function Image({ src, alt }) {
const hasWasm = typeof WebAssembly !== 'undefined';
if (!hasWasm) {
return <img src={src} alt={alt} />; // Fallback to default
}
return <SmartCropImage src={src} alt={alt} />;
}Performance on Mobile
Issue: Analysis slow on lower-end devices
Tips:
- Use debug mode sparingly (adds rendering overhead)
- Lazy loading is enabled by default (respects viewport)
- Image downsampling is aggressive (64×64) for performance
- WASM memory is automatically freed after analysis
Memory Leaks
Handled by default: The hook automatically calls module._free(ptr) after each analysis. No manual cleanup needed.
Black/Blank Canvas
Problem: Canvas context returns null
Solution: Ensure images are loaded and have valid dimensions:
<SmartCropImage
src={validUrl} // Not null/undefined
width={300} // Must be > 0
height={300} // Must be > 0
/>� Social Proof
If this project has helped you build better UIs, please consider giving it a star! ⭐
Your support helps us:
- 📈 Reach more developers
- 🚀 Continue improving the library
- 💪 Build confidence in the community
| Browser | Minimum Version | WASM Support | Status | |---------|-----------------|--------------|--------| | Chrome | 74+ | ✅ Full | Fully Supported | | Firefox | 79+ | ✅ Full | Fully Supported | | Safari | 14.1+ | ✅ Full | Fully Supported | | Edge | 79+ | ✅ Full | Fully Supported | | Opera | 61+ | ✅ Full | Fully Supported | | iOS Safari | 14.5+ | ✅ Full | Fully Supported | | Android Chrome | 74+ | ✅ Full | Fully Supported |
WebAssembly Support: Visit caniuse.com/wasm for detailed compatibility.
Fallback Strategy: Detect WASM and fall back to default image rendering if unavailable.
Known Limitations
- Single focal point per image (not multiple regions)
- Requires CORS-enabled image sources
- Downsamples large images to 64×64 (by design, for performance)
- Best results with properly exposed, well-lit photos
📄 License & Attribution
License: MIT © 2026
Author: Sargis Artashyan
Location: 🏔️ Gyumri, Armenia
Credits
- Algorithm: Saliency detection inspired by computer vision research
- Implementation: C++ with Emscripten toolchain
- Testing: Made possible by Unsplash photographers
Demo Images Attribution
- Photo 1: Kevin Noble — Short-coated brown dog on gray cliff
- Photo 2: Marco Montero Pisani — Red and yellow figures near fountain
- Photo 3: Muhammad-Taha Ibrahim — Girl standing beside bird cage
🤝 Contributing
We welcome contributions! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone repository
git clone https://github.com/sargisartashyanmain/react-smart-crop.git
cd react-smart-crop
# Install dependencies
npm install
# Build WASM module (requires Emscripten)
./scripts/build-wasm.sh
# Start dev server
npm run dev
# Build library
npm run build
# Run linter
npm run lint📞 Support & Questions
- 📖 Documentation: See examples in
/demo/App.tsx - 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
- 🎯 Demo: Live Demo
📊 Performance Metrics
- Analysis Time: ~50-200ms per image (depending on device)
- Bundle Size: ~45KB (minified + gzipped)
- WASM Module: ~35KB
- Memory Usage: ~2-5MB peak (cleaned up after analysis)
- Speedup: 20-30x faster than JavaScript implementation
Made with ❤️ in Armenia
