mefody.dev

Link to download

Sometimes I need to create a link that should show a system dialog to save the file. Browsers are pretty smart to open that dialog for some binaries, e.g. for archives or *.exe. But what if I want to download an image or some video?

Content-Disposition header

The most valid way is to add a Content-Disposition header on the server.

Content-Disposition: attachment; filename=kitten.jpg

When your browser finds the attachment value, it starts to download the file, not to show it.

But sometimes you can’t change any server configs, so we need a more browserish way to solve the problem.

download attribute

The easiest way to do this is to add the download attribute to your link.

If you just add it without any value, your browser will try to get the name of the file from its Content-Disposition header (yeah, it has high priority) or its path.

<a href="images/kitten.jpg" download>
    <img src="images/kitten-preview.jpg" alt="Kitten photo preview">
</a>

Try it: demo link.

You can set some value to download if you want to change the default name. It can help when you have some strange auto-generated URL like https://cdn/images/a1H5-st42-Av1f-rUles.

<a href="images/1h24v9lj.jpg" download="kitten">
    Download
</a>

Try it: demo link.

Important! All this attribute magic is not for cross-origin links. You can’t control the stuff from cross-origin sources due to security issues.

And remember that IE and old Safari are unaware of download. Check it.

blob: and data:

Another lifehack to help your users save pictures of kittens in a format convenient for them. If you use AVIF or WebP images on your site, it is a huge chance that no one will be able to save them on their computer or phone to view them later. More precisely, they can save the image, but they can’t view it. Very sad.

To help them, use data: or blob: URLs inside the href attribute.

Step 1. Draw the image on a canvas.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = new Image();
image.onload = function () {
    ctx.drawImage(image, 0, 0);

    // TODO: put the magic here
};
image.src = 'kitten-170.avif';

Step 2a. Save the image as a blob to the link href.

const blobLink = document.getElementById('blob-link');

canvas.toBlob(blob => {
    const blobUrl = URL.createObjectURL(blob);
    blobLink.href = blobUrl;
}, 'image/jpeg', 0.9);

Yes, I can save AVIF as JPEG. Cool, right? The user downloaded just 4 KB AVIF from a server and got his 13 KB JPEG on a client!

Step 2b. Save the image as a data to the link href.

Some browsers can’t use blobs, so you can help them with data-urls.

const dataLink = document.getElementById('data-link');

dataLink.href = canvas.toDataURL('image/jpeg', 0.9);

It’s even easier, but not so performant.

You can play with the full demo here:

Sources

Webmentions [?]

  1. Чтобы вызывать окно сохранения файла по клику на ссылку, можно обойтись атрибутом download. А если добавить немного JavaScript, то можно ещё и помочь пользователю сохранять понимаемые только браузерами AVIF- и WebP-картинки как понимаемый чем угодно JPEG.

  2. Pablo Lara H

    Link to download by Nikita Dubko @dark_mefody @dev_tip Create a link to download the file or save AVIF or WebP as JPEG. #ContentDisposition #downloadfiles #html #webdev #js mefody.dev/chunks/downloa…

  3. Šime Vidas

    @dark_mefody Re mefody.dev/chunks/downloa…, I just realized that in desktop Chrome and Firefox, the user can right-click on the <canvas> and select “Save image as… PNG”. Not bad.