← home
Image Optimization Tools
Image Optimization Tools
Overview
This directory contains tools for optimizing images on bankole.org with a focus on performance and GitHub Pages compatibility.
optimize-images.sh
Automated image optimization script that generates WebP versions and responsive image sizes.
Features
- WebP Conversion: Creates WebP versions with JPEG fallbacks
- Responsive Images: Generates 640w, 1280w, and 1920w sizes
- JPEG Optimization: Reduces file sizes while maintaining quality
- Batch Processing: Can process individual files or entire directories
Usage
# Optimize a single image
bash tools/optimize-images.sh path/to/image.jpg
# Optimize all images in a directory
bash tools/optimize-images.sh assets/images/posts/
# Preview what would be optimized (dry run)
bash tools/optimize-images.sh --dry-run image.jpg
# Custom quality settings
bash tools/optimize-images.sh --quality 85 --webp-quality 80 image.jpg
Configuration
Default settings in the script:
- Max Width: 1920px
- JPEG Quality: 90%
- WebP Quality: 85%
- Responsive Sizes: 1920w, 1280w, 640w
Output
For each input image, the script generates:
- Original optimized JPEG (if over 500KB or wider than 1920px)
- Example:
image.jpg
- Example:
- WebP version
- Example:
image.webp
- Example:
- Responsive JPEG versions
- Example:
image-640w.jpg,image-1280w.jpg,image-1920w.jpg
- Example:
- Responsive WebP versions
- Example:
image-640w.webp,image-1280w.webp,image-1920w.webp
- Example:
Requirements
- sips: Built into macOS
- cwebp: Install with
brew install webp
Example
# Before
boracay-beach.jpeg: 1.5MB (4032x3024)
# After running optimization
boracay-beach.jpg: 933KB (1920x1440)
boracay-beach.webp: 397KB
boracay-beach-1920w.jpg: 933KB
boracay-beach-1920w.webp: 397KB
boracay-beach-1280w.jpg: 431KB
boracay-beach-1280w.webp: 184KB
boracay-beach-640w.jpg: 108KB
boracay-beach-640w.webp: 47KB
Performance Impact
- JPEG Optimization: 38-70% file size reduction
- WebP Format: Additional 50-60% reduction vs optimized JPEG
- Responsive Images: Serve appropriate size based on viewport
- Total Page Weight Savings: Up to 4.2MB per page with images
Integration with Jekyll
The optimized images work with the enhanced _includes/lazy-image.html component:
<div class="lazy-image-wrapper">
<picture>
<source
type="image/webp"
data-srcset="/assets/images/posts/photo-640w.webp 640w,
/assets/images/posts/photo-1280w.webp 1280w,
/assets/images/posts/photo-1920w.webp 1920w"
sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
/>
<source
type="image/jpeg"
data-srcset="/assets/images/posts/photo-640w.jpg 640w,
/assets/images/posts/photo-1280w.jpg 1280w,
/assets/images/posts/photo-1920w.jpg 1920w"
sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
/>
<img
data-src="/assets/images/posts/photo.jpg"
alt="Description"
class="lazy-image "
loading="lazy"
width="1920"
height="1440"
/>
</picture>
<noscript>
<picture>
<source
type="image/webp"
srcset="/assets/images/posts/photo-640w.webp 640w,
/assets/images/posts/photo-1280w.webp 1280w,
/assets/images/posts/photo-1920w.webp 1920w"
sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
/>
<img
src="/assets/images/posts/photo.jpg"
srcset="/assets/images/posts/photo-640w.jpg 640w,
/assets/images/posts/photo-1280w.jpg 1280w,
/assets/images/posts/photo-1920w.jpg 1920w"
sizes="(max-width: 640px) 640px, (max-width: 1280px) 1280px, 1920px"
alt="Description"
class=""
width="1920"
height="1440"
/>
</picture>
</noscript>
</div>
<script>
(function() {
if (!window.lazyImageObserver) {
window.lazyImageObserver = {
init: function() {
var images = document.querySelectorAll('img.lazy-image[data-src]');
var sources = document.querySelectorAll('source[data-srcset]');
// Function to load an image and its sources
var loadImage = function(img) {
// Load source elements first
var picture = img.closest('picture');
if (picture) {
var pictureSources = picture.querySelectorAll('source[data-srcset]');
pictureSources.forEach(function(source) {
if (source.dataset.srcset) {
source.srcset = source.dataset.srcset;
source.removeAttribute('data-srcset');
}
});
}
// Load img element
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
img.classList.add('lazy-loaded');
if (window.performance && window.performance.mark) {
performance.mark('image-loaded');
}
}
};
if ('loading' in HTMLImageElement.prototype) {
// Native lazy loading support
images.forEach(function(img) {
loadImage(img);
});
} else if ('IntersectionObserver' in window) {
// IntersectionObserver fallback
var imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var img = entry.target;
loadImage(img);
observer.unobserve(img);
}
});
}, {
rootMargin: '50px'
});
images.forEach(function(img) {
imageObserver.observe(img);
});
} else {
// Fallback for older browsers - load everything
images.forEach(function(img) {
loadImage(img);
});
}
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', window.lazyImageObserver.init);
} else {
window.lazyImageObserver.init();
}
}
})();
</script>
The component automatically:
- Serves WebP to supporting browsers
- Uses responsive srcset for different viewport sizes
- Lazy-loads images as users scroll
- Prevents layout shift with width/height attributes
Testing
Run the image optimization test suite:
bash tests/image-optimization-tests.sh
Tests verify:
- WebP versions exist for all images
- Image file sizes are optimized
- Responsive versions are generated
- Lazy-image component has required features
- Optimization script is properly configured
Best Practices
- Always optimize before committing
bash tools/optimize-images.sh assets/images/posts/new-image.jpg git add assets/images/posts/ -
Use descriptive alt text in the lazy-image component
-
Specify width and height to prevent layout shift
-
Test on multiple devices to verify responsive images load correctly
- Check file sizes after optimization:
ls -lh assets/images/posts/ | grep "new-image"
Troubleshooting
WebP files not generated
- Ensure cwebp is installed:
brew install webp - Check file permissions in the output directory
Images too large
- Adjust JPEG quality:
--quality 85 - Verify source image dimensions
- Consider manual editing for specific images
Responsive versions missing
- Image must be at least 640px wide
- Check script output for errors
- Verify sips command is available
Performance Metrics
| Metric | Before | After | Improvement |
|---|---|---|---|
| Largest image | 1.7MB | 424KB (WebP) | 75% smaller |
| Average image size | 1.6MB | 436KB | 73% smaller |
| Total payload (3 images) | 4.8MB | 1.3MB | 73% reduction |
| Mobile viewport (640w) | 4.8MB | 156KB | 97% reduction |
Future Enhancements
- AVIF format support
- Automatic image dimension detection for lazy-image includes
- Pre-commit hook for automatic optimization
- CI/CD integration for pull requests
- Blur-up placeholder generation