<img
src="photo-800.webp"
srcset="
photo-400.webp 400w,
photo-800.webp 800w,
photo-1200.webp 1200w"
sizes="
(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px"
loading="lazy"
alt="User photo"
/>
<!-- With format fallback -->
<picture>
<source
srcset="photo-800.avif"
type="image/avif" />
<source
srcset="photo-800.webp"
type="image/webp" />
<img src="photo-800.jpg"
alt="User photo" />
</picture>
function LazyImage({ src, alt }) {
const imgRef = useRef(null);
const [loaded, setLoaded]
= useState(false);
useEffect(() => {
const observer =
new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
imgRef.current.src = src;
observer.unobserve(
imgRef.current);
}
},
{ rootMargin: '200px' }
// Start loading 200px before visible
);
observer.observe(imgRef.current);
return () => observer.disconnect();
}, [src]);
return (
<img
ref={imgRef}
alt={alt}
onLoad={() => setLoaded(true)}
className={loaded
? 'fade-in' : 'placeholder'}
/>
);
}
function exportAsWebP(canvas, quality = 0.8) {
return new Promise((resolve) => {
canvas.toBlob(
(blob) => {
const url =
URL.createObjectURL(blob);
resolve({ blob, url });
},
'image/webp',
quality
);
});
}
// Compare sizes
const jpeg = await exportAs(
canvas, 'image/jpeg', 0.9);
const webp = await exportAsWebP(
canvas, 0.8);
console.log(
`JPEG: ${(jpeg.blob.size / 1024)
.toFixed(0)}KB`);
console.log(
`WebP: ${(webp.blob.size / 1024)
.toFixed(0)}KB`);
| Metric | Before | After |
|---|---|---|
| Gallery page weight | 40MB | 3.2MB |
| Initial images loaded | 50 (all) | 8 (visible) |
| Thumbnail size | 2MB each | 40KB each |
| LCP | 8.2s | 1.4s |
git switch -c feature/PIXELCRAFT-061-image-optimization
git add src/ server/
git commit -m "Add WebP, srcset, lazy loading (PIXELCRAFT-061)"
git push origin feature/PIXELCRAFT-061-image-optimization
# PR → Review → Merge → Close ticket ✅
Image compression is applied mathematics.
Every image format uses a different strategy to throw away information humans won't notice.