Image Downloader & Preview
A Chrome extension to preview and download images from any webpage in a couple of clicks.
What makes it good
- 1Preview every image on a page
- 2Download images in one click
- 3Works on any website
How it works
How it works
A scanner is injected on demand — never on page load — and collects every image the page references, not just the ones already painted. It reads currentSrc, walks srcset for the largest candidate, checks the common lazy-loading attributes, and sweeps CSS background images.
function bestFromSrcset(srcset) {
let best = null, bestScore = -1;
for (const part of srcset.split(',')) {
const [url, descriptor = ''] = part.trim().split(/\s+/);
// "1024w" → 1024, "2x" → 2000, so widths and densities never mix badly
const score = descriptor.endsWith('w') ? parseFloat(descriptor)
: descriptor.endsWith('x') ? parseFloat(descriptor) * 1000
: 1;
if (score > bestScore) { bestScore = score; best = url; }
}
return best;
}Two download paths, for one specific reason
A single image goes through the downloads API. Chrome fetches it with the page's own credentials, so there is no CORS involved and no host permission needed:
chrome.downloads.download({ url: img.src, filename, saveAs: false });A ZIP is different — the bytes have to be in the extension's hands to compress them. That means fetch, which means host permissions, which are requested for exactly the origins involved at the moment you click, and never up front:
const origins = [...new Set(images.map((i) => new URL(i.src).origin + '/*'))];
if (!(await chrome.permissions.contains({ origins }))) {
await chrome.permissions.request({ origins });
}
const blob = await (await fetch(img.src, { cache: 'force-cache' })).blob();
zip.file(uniqueName(filename, used), blob);Fetches run sequentially rather than all at once: two hundred parallel requests to one host gets throttled, and the progress bar stops meaning anything.
Naming
Modern CDN URLs frequently have no file extension — /photo/1234?w=800 is an image. Filtering on a .jpg suffix, which is the obvious approach, silently discards most images on most sites. Names are derived from the response's content type instead, and de-duplicated so several files called photo.jpg do not collapse into one ZIP entry.
const EXT_BY_MIME = {
'image/jpeg': 'jpg', 'image/png': 'png', 'image/webp': 'webp',
'image/avif': 'avif', 'image/gif': 'gif', 'image/svg+xml': 'svg',
};In the popup
Filter by URL or alt text, hide anything below a minimum dimension, sort by size, and a "load all" button that scrolls the page to trigger lazy loading before rescanning.