html, jak zapisać obraz <img> do pliku na dysku
download, Canvas API + toBlob, File System Access API) albo zapis po stronie serwera (PHP, Node.js, …).Kluczowe punkty
• <a download> – najszybsze, ale wymaga tego samego pochodzenia (Same-Origin).
• Canvas API → toBlob() / toDataURL() – pozwala pobrać dowolny obraz renderowany w przeglądarce, o ile nie blokuje CORS.
• File System Access API – najnowszy sposób na bezpośredni zapis pliku na dysku (Chromium ≥ 86).
• Zewnętrzne domeny ⇒ sprawdzić nagłówki CORS lub użyć proxy po stronie serwera.
Przeglądarki chronią system plików użytkownika:
• żaden skrypt nie może zapisać pliku w tle;
• użytkownik musi zainicjować lub zatwierdzić operację (kliknięcie, okno wyboru katalogu).
| Metoda | Kod (skrót) | Plusy | Minusy | Kiedy wybrać |
|---|---|---|---|---|
<a download> |
<a href="img.jpg" download="nazwa.jpg">Pobierz</a> |
1 linijka, brak JS | Tylko same-origin; brak kontroli nad nazwą/formatem poza download |
Gdy obraz i strona są na tym samym serwerze |
| Canvas API | patrz kod A | Działa dla <img>, <video>, SVG; można zmienić format, kompresję |
CORS; duże obrazy ⇒ pamięć | Gdy trzeba modyfikować lub konwertować obraz |
fetch → Blob → URL.createObjectURL |
kod B | Pobiera dowolny plik (PNG/JPG/WebP/…); nie wymaga Canvas | CORS; brak modyfikacji | Gdy zależy na oryginalnych danych binarnych |
| File System Access API | kod C | Bezpośredni zapis do wskazanego przez użytk. katalogu | Chrome/Edge/Opera; wymaga HTTPS i user gesture | Aplikacje PWA, edytory online |
| Biblioteka FileSaver.js | saveAs(blob, 'nazwa.png') |
Cross-browser (IE 10+); ukrywa różnice | Dodatkowa zależność | Szybki fallback dla starszych przeglądarek |
Kod A – Canvas + JPEG 80 %
async function saveImgCanvas(id, fname='obraz.jpg', quality=0.8){
const img = document.getElementById(id);
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext('2d').drawImage(img,0,0);
const blob = await new Promise(r => canvas.toBlob(r,'image/jpeg',quality));
const url = URL.createObjectURL(blob);
triggerDownload(url, fname);
}
function triggerDownload(url, fname){
const a = Object.assign(document.createElement('a'), {href:url, download:fname});
document.body.append(a); a.click(); a.remove();
URL.revokeObjectURL(url);
}
Kod B – fetch → Blob
async function downloadFromSrc(src,fname='plik'){
const res = await fetch(src,{mode:'cors'});
if(!res.ok) throw new Error(res.status);
const blob = await res.blob();
triggerDownload(URL.createObjectURL(blob), fname);
}
Kod C – File System Access (Chromium)
async function saveWithFSA(imgId){
const img = document.getElementById(imgId);
const handle = await window.showSaveFilePicker({suggestedName:'obraz.png',
types:[{description:'PNG',accept:{'image/png':['.png']}}]});
const writable = await handle.createWritable();
const blob = await (await fetch(img.src)).blob();
await writable.write(blob);
await writable.close();
}
Jeżeli:
• obraz jest z innej domeny bez CORS,
• trzeba go zapisać „w tle”,
przekazujemy URL do backendu i tam zapisujemy go na dysk:
PHP
file_put_contents('obrazy/'.basename($url), file_get_contents($url));
Node.js (HTTPS)
https.get(url,res=>res.pipe(fs.createWriteStream('img.jpg')));
• File System Access API staje się standardem w PWA – umożliwia desktop-like workflow w przeglądarce.
• WebCodecs i WebGPU generują obrazy/ramki, które często zapisuje się właśnie przez Canvas → Blob lub FS API.
• Format WebP/AVIF zyskuje na popularności; Canvas toBlob('image/avif') ma już wstępne wsparcie w Chrome 121+.
• Coraz częściej aplikacje frontendowe korzystają z WebAssembly (np. libvips/wasm) do edycji przed zapisem.
• Same-Origin Policy – przeglądarka pozwala odczytać piksele tylko, jeśli protocol + domain + port są identyczne lub header Access-Control-Allow-Origin zezwala.
• canvas.toDataURL() zwraca Base64; toBlob() zwraca binarne dane – lepsze dla dużych plików (mniej RAM).
• URL.revokeObjectURL() – konieczne, by zwolnić pamięć.
• FileSaver.js pod IE używa msSaveOrOpenBlob.
• Prawa autorskie – pobieranie obrazów chronionych bez licencji jest nielegalne.
• RODO/CPRA – jeśli obraz zawiera dane osobowe (np. twarze), jego pobieranie lub przechowywanie wymaga podstawy prawnej.
• Bezpieczeństwo – unikać zapisywania w stałych lokalizacjach; FS API zawsze wymusza wybór katalogu przez użytkownika.
<a download>). curl -I URL). toBlob() zamiast toDataURL(). display: standalone, by FS API działało płynnie. download, ale nie FS API.• FS API nie jest jeszcze standardem W3C; w Safari/Firefox dostępne wyłącznie za flagami.
• Stare przeglądarki (IE ≤ 9) – brak download, konieczny BlobBuilder lub serwer.
• Przy obrazach z kamer (getUserMedia) potrzebny jest HTTPS.
• Monitoruj specyfikację File System Access (WICG).
• Sprawdź projekt Native File System Adapter w Firefox Nightly.
• Eksperymentuj z WebAssembly (Squoosh, Sharp/wasm) w celu bezstratnej kompresji przed zapisem.
• Rozważ Service Workers do buforowania i „offline save”.
Sam HTML nie zapisze pliku. Najłatwiej dodać link z atrybutem download. Jeżeli potrzebna jest manipulacja obrazem – renderujemy go na Canvas i zapisujemy przez toBlob(). Dla aplikacji przypominających desktop można wykorzystać File System Access API, a w sytuacjach zablokowanych przez CORS – pobrać obraz na serwer i tam go zapisać. We wszystkich przypadkach konieczna jest świadoma interakcja użytkownika oraz respektowanie praw autorskich.