How I solved a common content creator problem using vanilla JavaScript and client-side processing
The Problem That Started It All
As a developer, I kept hearing the same complaints from photographer and designer friends:
"Why do watermarking tools cost $20/month for something so simple?"
"I don't want to upload my client's photos to some random website"
"Batch processing is locked behind premium tiers"
Sound familiar? I thought so too. So I decided to build something better.
Enter ImageMark 🚀
ImageMark is a completely client-side watermarking tool that processes your images without ever sending them to a server. No accounts, no subscriptions, no privacy concerns.
🔗 Try it live | 📁 GitHub repo
The Tech Stack (Keeping It Simple)
I deliberately chose vanilla JavaScript over frameworks to keep the bundle size small and loading fast:
Vanilla JavaScript - No framework overhead
Canvas API - For image manipulation and watermark rendering
Web Workers - Non-blocking batch processing
FileReader API - Client-side file handling
CSS Grid - Responsive layout
Key Technical Challenges & Solutions
- Client-Side Image Processing The biggest challenge was handling image manipulation entirely in the browser. Here's how the core watermarking function works:
function applyWatermark(imageData, watermarkText, options) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas dimensions to match image
canvas.width = imageData.width;
canvas.height = imageData.height;
// Draw original image
ctx.drawImage(imageData, 0, 0);
// Smart positioning based on brightness analysis
const position = calculateOptimalPosition(ctx, canvas.width, canvas.height);
// Apply watermark with proper opacity and blending
ctx.globalAlpha = options.opacity;
ctx.font = `${options.fontSize}px Arial`;
ctx.fillStyle = options.color;
ctx.fillText(watermarkText, position.x, position.y);
return canvas.toDataURL();
}
- Smart Watermark Placement Instead of just slapping watermarks in corners, I implemented brightness analysis to find the optimal placement:
function calculateOptimalPosition(ctx, width, height) {
// Sample different regions of the image
const regions = [
{ x: width * 0.1, y: height * 0.1 }, // Top-left
{ x: width * 0.9, y: height * 0.1 }, // Top-right
{ x: width * 0.1, y: height * 0.9 }, // Bottom-left
{ x: width * 0.9, y: height * 0.9 } // Bottom-right
];
let bestRegion = regions[0];
let bestContrast = 0;
regions.forEach(region => {
const brightness = getRegionBrightness(ctx, region.x, region.y);
const contrast = calculateContrast(brightness, watermarkColor);
if (contrast > bestContrast) {
bestContrast = contrast;
bestRegion = region;
}
});
return bestRegion;
}
- Batch Processing with Web Workers To keep the UI responsive during batch operations, I offloaded the heavy lifting to Web Workers:
// Main thread
const worker = new Worker('watermark-worker.js');
worker.postMessage({
images: selectedFiles,
watermarkOptions: userSettings
});
worker.onmessage = (event) => {
const { processedImage, progress } = event.data;
updateProgressBar(progress);
downloadProcessedImage(processedImage);
};
Privacy by Design
The entire application follows privacy-first principles:
✅ No server uploads - Images never leave your device
✅ No user accounts - Start using immediately
✅ No tracking - Zero analytics or data collection
✅ Open source - Full transparency in code
Performance Optimizations
File Size Matters
Vanilla JS keeps the bundle under 50KB gzipped
Lazy loading for non-critical features
Efficient canvas operations to minimize memory usage
Mobile Responsiveness
Touch-friendly drag & drop zones
Responsive design that works on tablets
Optimized for mobile browsers
Lessons Learned
- Canvas API Gotchas Working with different image formats revealed several browser inconsistencies. EXIF data handling was particularly tricky across different devices.
- User Experience is Everything Technical capabilities mean nothing if users can't figure out how to use your tool. I spent as much time on UX as on the actual watermarking logic.
- Progressive Enhancement Works Starting with core functionality and adding features incrementally made development much smoother. What's Next? Current roadmap includes:
Custom watermark templates
Video watermarking support
Advanced positioning controls
Export format options
Try It Out!
ImageMark is live at imagemark.app and the full source code is available on GitHub.
I'd love to hear your feedback! What features would make this more useful for your workflow?