@swiftlyme/image-uploader
v1.0.1
Published
Angular image uploader with crop, drag & reorder support
Downloads
58
Maintainers
Readme
ImageUploaderLib
A powerful, feature-rich Angular image uploader component with drag-and-drop, cropping, editing, PDF support, and replacement capabilities.
🚀 Features
- ✅ Drag & Drop - Reorder images with smooth animations using Angular CDK
- ✅ Advanced Crop Editor - Crop, rotate, and flip images with live canvas preview
- ✅ Image Replacement - Replace existing images without losing position
- ✅ PDF Support - Upload, preview, and view PDF documents
- ✅ Multiple Aspect Ratios - 1:1, 16:9, 4:3, 9:16, or custom ratios
- ✅ Free Crop Mode - Crop images to any size without constraints
- ✅ Slider Layout - Horizontal scrollable carousel with navigation buttons
- ✅ Grid Layout - Responsive grid that adapts to container size
- ✅ Image Compression - Reduce file sizes with adjustable compression
- ✅ Download Support - Download edited/cropped images
- ✅ Granular Permissions - Toggle edit, replace, delete, download, and drag features
- ✅ Event Emissions - Track upload, replace, delete, and change events
- ✅ Pre-load URLs - Initialize with existing image URLs
- ✅ Container Queries - Smart responsive behavior based on parent container size
- ✅ Mobile Responsive - Optimized for all screen sizes, including micro views
- ✅ Accessibility - ARIA labels and keyboard support
📦 Installation
npm install @swiftlyme/image-uploaderDependencies
Ensure you have the following peer dependencies installed:
npm install @angular/cdk
npm install bootstrap@5
npm install bootstrap-iconsStyle Requirements
Add Bootstrap 5 and Bootstrap Icons to your angular.json:
{
"styles": ["node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap-icons/font/bootstrap-icons.css", "src/styles.css"]
}🎯 Quick Start
1. Import the Module
import { ImageUploaderLibComponent } from "@swiftlyme/image-uploader";
@NgModule({
imports: [
ImageUploaderLibComponent,
// ... other imports
],
})
export class AppModule {}2. Add to Your Template
<lib-image-uploader-lib [maxImages]="6" [aspectRatio]="1" [layoutMode]="'grid'" (imagesChange)="handleImagesChange($event)" (imageUpload)="onImageUpload($event)" (imageReplace)="onImageReplace($event)" (imageDelete)="onImageDelete($event)" (imageEdit)="onImageEdit($event)"> </lib-image-uploader-lib>3. Handle Events in Component
export class YourComponent {
handleImagesChange(images: any[]) {
console.log("Images updated:", images);
}
onImageUpload(image: any) {
console.log("New image uploaded:", image);
}
onImageReplace(event: any) {
console.log("Image replaced:", event);
// event contains: { old, new, index }
}
onImageDelete(image: any) {
console.log("Image deleted:", image);
}
onImageEdit(image: any) {
console.log("Image edited:", image);
}
}📚 API Reference
Input Properties
| Property | Type | Default | Description |
| ----------------- | ---------- | -------- | ---------------------------------------------------------------------- |
| maxImages | number | 1 | Maximum number of images/files allowed |
| aspectRatio | number | 1 | Aspect ratio for cropping (e.g., 1, 16/9, 4/3) |
| enableFreeCrop | boolean | false | Allow free-form cropping without ratio constraints |
| layoutMode | string | 'grid' | Layout style: 'grid' or 'slider' |
| allowDrag | boolean | true | Enable drag-and-drop reordering |
| allowEdit | boolean | true | Show edit button for cropping/rotating images (not available for PDFs) |
| allowReplace | boolean | true | Show replace button to swap images or PDFs |
| allowDelete | boolean | true | Show delete button |
| allowDownload | boolean | true | Show download button |
| compressionRate | number | 0 | Image compression percentage (0-95, 0 = no compression) |
| imageUrls | string[] | [] | Pre-load images from URLs |
Output Events
| Event | Payload | Description |
| -------------- | --------------------- | --------------------------------------------------------- |
| imagesChange | any[] | Emitted when images array changes (any operation) |
| imageUpload | any | Emitted when a new image/PDF is uploaded to an empty slot |
| imageReplace | { old, new, index } | Emitted when an existing image/PDF is replaced |
| imageDelete | any | Emitted when an image/PDF is deleted |
| imageEdit | any | Emitted when an image/PDF is edited (any crop) |
🎨 Layout Modes
Grid (Default)
Responsive grid that automatically adapts to container size using CSS Container Queries.
<lib-image-uploader-lib [layoutMode]="'grid'" [maxImages]="6"></lib-image-uploader-lib>Smart Responsive Features:
- Wide containers (>350px): Shows multiple columns based on screen size
- Narrow containers (<350px): Automatically stacks images vertically
- Micro containers (<150px): Hides text, shows icon-only controls
Slider
Horizontal scrollable carousel with navigation buttons and snap-to-center behavior.
<lib-image-uploader-lib [layoutMode]="'slider'" [maxImages]="10"></lib-image-uploader-lib>Features:
- Smooth scroll-snap navigation
- Previous/Next buttons with auto-disable at boundaries
- Swipe support on mobile devices
- Hides scrollbar for clean appearance
🎨 Column Sizing
The grid layout dynamically adjusts based on image index and screen size:
| Image Position | Desktop (lg+) | Tablet (md) | Mobile (sm) | | -------------- | ------------- | -------------- | -------------- | | First Image | 6 cols (50%) | 12 cols (100%) | 12 cols (100%) | | Other Images | 3 cols (25%) | 6 cols (50%) | 12 cols (100%) |
For slider mode, all images maintain consistent sizing.
📄 PDF Support
The component fully supports PDF uploads with special handling:
Upload PDF
<lib-image-uploader-lib [maxImages]="5" accept="image/*,application/pdf"> </lib-image-uploader-lib>PDF Features
- ✅ PDF icon preview with filename
- ✅ View button opens PDF in new tab
- ✅ Replace functionality
- ✅ Delete functionality
- ✅ Drag and reorder alongside images
- ❌ Edit/crop not available (images only)
Detect PDF in Events
onImageUpload(file: any) {
if (file.file?.type === 'application/pdf') {
console.log('PDF uploaded:', file.file.name);
} else {
console.log('Image uploaded');
}
}🔧 Advanced Usage
Custom Aspect Ratio
<lib-image-uploader-lib [aspectRatio]="21/9" [maxImages]="4"> </lib-image-uploader-lib>Free Crop Mode
<lib-image-uploader-lib [enableFreeCrop]="true" [maxImages]="3"> </lib-image-uploader-lib>Image Compression
<lib-image-uploader-lib [compressionRate]="50" [maxImages]="5"> </lib-image-uploader-lib>Compression ranges from 0 (no compression) to 95 (maximum compression)
Pre-loaded Images
export class YourComponent {
existingImages = ["https://example.com/image1.jpg", "https://example.com/image2.jpg", "https://example.com/image3.jpg"];
}<lib-image-uploader-lib [imageUrls]="existingImages" [maxImages]="5"> </lib-image-uploader-lib>Granular Permissions
<lib-image-uploader-lib [allowDrag]="true" [allowEdit]="true" [allowReplace]="true" [allowDelete]="true" [allowDownload]="false"> </lib-image-uploader-lib>Replace Event Handling
export class YourComponent {
onImageReplace(event: any) {
const { old, new: newImage, index } = event;
// Delete old file from server
this.deleteFromServer(old.originalUrl);
// Upload new file to server
this.uploadToServer(newImage.file, index);
// Update database
this.updateDatabase(index, newImage);
// Show notification
this.showNotification(`File ${index + 1} replaced successfully`);
}
}🎯 Real-World Examples
E-commerce Product Gallery
<lib-image-uploader-lib [maxImages]="8" [aspectRatio]="1" [layoutMode]="'slider'" [allowDrag]="true" [allowReplace]="true" [compressionRate]="30" (imagesChange)="updateProductImages($event)" (imageReplace)="handleProductImageReplace($event)"> </lib-image-uploader-lib>User Profile Picture
<lib-image-uploader-lib [maxImages]="1" [aspectRatio]="1" [layoutMode]="'grid'" [allowDelete]="false" [allowReplace]="true" (imageUpload)="uploadAvatar($event)" (imageReplace)="updateAvatar($event)"> </lib-image-uploader-lib>Photo Album
<lib-image-uploader-lib [maxImages]="20" [enableFreeCrop]="true" [layoutMode]="'grid'" [allowDrag]="true" [allowDownload]="true" (imagesChange)="saveAlbum($event)"> </lib-image-uploader-lib>Document Management (Images + PDFs)
<lib-image-uploader-lib [maxImages]="15" [aspectRatio]="210/297" [layoutMode]="'grid'" [allowReplace]="true" [compressionRate]="60" (imagesChange)="processDocuments($event)"> </lib-image-uploader-lib>Sidebar Upload (Narrow Container)
<div class="col-lg-2">
<lib-image-uploader-lib [maxImages]="5" [layoutMode]="'grid'" [aspectRatio]="1"> </lib-image-uploader-lib>
</div>Automatically stacks vertically and adjusts controls for narrow spaces
🎨 Edit Modal Features
The fullscreen edit modal provides comprehensive image editing:
Controls Available
- Crop - Drag overlay to reposition, drag corners to resize
- Rotate Left - Rotate 90° counter-clockwise
- Rotate Right - Rotate 90° clockwise
- Flip Horizontal - Mirror image horizontally
- Flip Vertical - Mirror image vertically
Modal Features
- ✅ Fullscreen overlay (95vw × 95vh)
- ✅ Canvas-based rendering
- ✅ Live preview with grid overlay
- ✅ Resize handles on all corners
- ✅ Dark background for better visibility
- ✅ Keyboard support (ESC to close)
Usage
<!-- Edit button appears automatically when allowEdit is true -->
<lib-image-uploader-lib [allowEdit]="true"></lib-image-uploader-lib>🖱️ Drag & Drop Features
Reordering
- Drag images to reorder them
- Visual drag preview with shadow
- Smooth animations during drop
- Works in both grid and slider modes
Drag Handles
- Full card is draggable when
allowDragistrue - Controls remain clickable during drag operations
- Automatic z-index management
Disable Dragging
<lib-image-uploader-lib [allowDrag]="false"></lib-image-uploader-lib>📐 Container Query Responsiveness
The component uses CSS Container Queries for intelligent responsive behavior:
Breakpoints
- >350px: Normal multi-column grid
- <350px: Single column vertical stack
- <150px: Micro view (icon-only controls)
Micro View Optimizations
- Upload text hidden
- Icon scaled down to 1.5rem
- Edit/Delete buttons reduced to 24×24px
- 4px button spacing
Example: Responsive Sidebar
<div class="sidebar" style="width: 200px;">
<lib-image-uploader-lib [maxImages]="3"></lib-image-uploader-lib>
<!-- Automatically uses single-column layout -->
</div>🎨 Styling & Customization
The component uses Bootstrap 5 for styling. Customize appearance by:
1. Override Bootstrap Variables
// styles.scss
$primary: #6366f1;
$warning: #f59e0b;
$danger: #ef4444;
$info: #0dcaf0;
@import "bootstrap/scss/bootstrap";2. Custom CSS Classes
/* Customize upload slot */
.upload-slot {
border-color: #8b5cf6 !important;
}
.upload-slot:hover {
background-color: #f3e8ff !important;
}
/* Customize control buttons */
.controls-overlay button {
opacity: 0.95 !important;
}
/* Customize edit modal */
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}3. Card Customization
/* Adjust card styling */
.card {
border-radius: 12px !important;
overflow: hidden;
}
.card-body {
padding: 0.5rem !important;
}📊 Image Object Structure
When images/PDFs are emitted through events, they have this structure:
{
id: string; // Unique identifier
originalUrl: string; // Base64 data URL or external URL
croppedUrl: string | null; // Base64 data URL of cropped image (null for PDFs)
previewUrl?: string; // Optional preview URL for PDFs
file: File | null; // Original File object (null for URL-based items)
isRemote?: boolean; // Flag indicating if loaded from URL
}Check File Type
function isPdf(slot: any): boolean {
return slot?.file?.type === "application/pdf";
}🚦 Event Flow
User Action → Event Emitted → Payload
─────────────────────────────────────────────────────────────────
Upload to empty slot → imageUpload → image/pdf
Replace existing → imageReplace → { old, new, index }
Delete image/PDF → imageDelete → image/pdf
Drag to reorder → imagesChange → images[]
Crop/edit save → imagesChange → images[]⚙️ Building the Library
Development Build
ng build image-uploader-libProduction Build
ng build image-uploader-lib --configuration productionBuild artifacts will be placed in the dist/ directory.
📤 Publishing to NPM
Build the library:
ng build image-uploader-lib --configuration productionNavigate to dist directory:
cd dist/image-uploader-libUpdate version in package.json (if needed)
Publish to npm:
npm publish
🧪 Testing
Unit Tests
ng test image-uploader-libEnd-to-End Tests
ng e2e🛠 Troubleshooting
Images not uploading?
- Check that
maxImagesis not exceeded - Verify file type is an image or PDF
- Check browser console for errors
Replace button not showing?
- Ensure
allowReplaceistrue - Verify the slot has an image/PDF (not empty)
- Check that the file has finished loading
Crop editor not working?
- Make sure
allowEditistrue - Crop is only available for images (not PDFs)
- For external URLs, ensure CORS is enabled on the image server
- Check that the image has loaded successfully
Download not working?
- External images may have CORS restrictions
- Check browser console for fetch errors
- For remote images, the browser may open them instead of downloading
Slider buttons not working?
- Ensure
layoutModeis set to'slider' - Check that there are enough images to scroll
- Buttons auto-disable at scroll boundaries
PDF not viewing?
- Check that popup blockers are not preventing the new tab
- Verify the PDF file is valid
- Check browser console for errors
Controls not visible in narrow containers?
- Container queries require modern browser support
- Check that CSS is properly loaded
- Verify Bootstrap Icons are included
📋 Browser Support
- ✅ Chrome (latest) - Full support
- ✅ Firefox (latest) - Full support
- ✅ Safari (latest) - Full support
- ✅ Edge (latest) - Full support
- ✅ Mobile browsers (iOS Safari, Chrome Mobile) - Full support
- ⚠️ Container Queries require modern browsers (Chrome 105+, Safari 16+, Firefox 110+)
🎯 Performance Tips
Image Compression
- Use
compressionRateto reduce file sizes - Recommended: 30-50 for web, 60-70 for thumbnails
Lazy Loading
For large galleries, consider loading images on demand:
// Load first batch
initialImages = imageUrls.slice(0, 6);
// Load more on scroll
loadMore() {
this.images.push(...imageUrls.slice(6, 12));
}Optimize Canvas Rendering
- Crop operations use canvas for better performance
- Automatically handles high-DPI displays
🤝 Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -am 'Add new feature' - Push to the branch:
git push origin feature/my-feature - Submit a pull request
📝 Changelog
v2.1.0 (Latest)
- ✨ Added PDF upload and preview support
- ✨ Added slider layout mode with navigation buttons
- ✨ Implemented CSS Container Queries for smart responsiveness
- ✨ Added micro-view optimizations for narrow containers
- ✨ Enhanced scroll-snap behavior in slider mode
- 🎨 Improved upload placeholder with responsive text
- 🎨 Added gradient background to modal header
- 🐛 Fixed fullscreen modal positioning
- 🐛 Fixed resize handles visibility
- 🐛 Fixed control buttons in micro views
v2.0.0
- ✨ Added image replacement feature
- ✨ New
imageReplaceevent emission - ✨ New
allowReplaceinput property - 🎨 Yellow warning button for replace action
- 🐛 Fixed file input handling for replacements
- 📚 Updated documentation
v1.0.0
- 🎉 Initial release
- ✨ Drag and drop support
- ✨ Crop/edit functionality
- ✨ Grid layout mode
- ✨ Image compression
📄 License
MIT License - feel free to use this library in your projects.
🙏 Acknowledgments
- Built with Angular
- Drag & Drop powered by Angular CDK
- Styled with Bootstrap 5
- Icons from Bootstrap Icons
- Canvas-based image editing
📞 Support
For issues, questions, or feature requests, please visit ([email protected])
🔗 Links
Made with ❤️ using Angular
