Resize before you compress: a four-step routine for lighter web images
Images are usually the heaviest files a page loads, and a heavy page feels slow, especially on a phone. The most common cause is not a missing compression setting. It is a 4000 px phone photo sitting in an 800 px column.
Images are usually the heaviest files a page loads, and a heavy page feels slow, especially on a phone. The most common cause is not a missing compression setting. It is a 4000 px phone photo sitting in an 800 px column. Here is the order I work in, and the one function that does the fitting.
1. Size the image to where it appears
Find the widest the image is ever displayed: an 800 px article column, a full-width banner. Scale the longest side down to about that width, or up to double it if you want crisp results on high-density screens. Keep the proportions and never enlarge.
Dropping pixels nobody will see is the biggest saving available. A phone photo that is 4000 px wide and shown in an 800 px column can go to 1600 px and still look sharp on a high-density screen, carrying far fewer pixels than the original. If you are unsure how wide a slot really is, check the layout in your browser's inspector.
2. Then set the quality
For JPG and WebP, start at 75% and compare the before and after sizes. Most photos look nearly the same at 70 to 80% as at 100%, at a fraction of the size. Lower it a notch at a time, and always re-encode from the original file, so repeated tries do not stack up quality loss.
PNG ignores a quality setting because it is lossless. For a PNG, resizing is the only lever. A photo saved as PNG is usually far bigger than the same photo as JPG, so convert photos; keep PNG for screenshots, text and logos where sharp edges and transparency matter.
3. Fit a size limit
Forms and portals love a hard limit ("under 100 KB"). There is no magic setting, so search for it: try qualities from high to low and keep the first result that fits, and if even the lowest quality is too big, shrink the pixels and try again.
async function fitToBytes(file, maxBytes, type = "image/jpeg") {
const bitmap = await createImageBitmap(file);
let scale = 1;
while (scale > 0.1) {
const w = Math.round(bitmap.width * scale);
const h = Math.round(bitmap.height * scale);
const canvas = new OffscreenCanvas(w, h);
canvas.getContext("2d").drawImage(bitmap, 0, 0, w, h);
for (let q = 8; q >= 1; q--) {
const blob = await canvas.convertToBlob({ type, quality: q / 10 });
if (blob.size <= maxBytes) return blob;
}
scale *= 0.8;
}
return null; // could not fit, say so instead of returning a postage stamp
}
It always starts from the original bitmap, so nothing compounds. Two honest limits: a JPG has no transparency, so transparent areas of a PNG turn black or white depending on how you paint the canvas, and very detailed photos (foliage, crowds) need more shrinking than simple ones.
4. Finish in the page
<img src="hero-1600.webp" width="1600" height="900" alt="..." />
<img src="below-fold.webp" width="800" height="450" loading="lazy" alt="..." />
Width and height stop the layout jumping while the image loads. loading="lazy" belongs on images below the fold, not on the main image at the top. Use descriptive file names and alt text. Then reload on a phone-sized window; if it still feels heavy, shrink the largest remaining images first.
Which format
For photos, WebP is usually smaller than JPG at similar quality and all current major browsers support it; JPG is the safe fallback. For screenshots, text and logos, PNG keeps edges sharp and handles transparency, and WebP supports transparency too.
I build a small browser tool that follows this routine, so I can do it without installing anything: set a max width, a quality or a target size in KB, and it compresses in the page with nothing uploaded. The routine above is the same one, written out: compress images for your website.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.