一番カンタンにダウンロードを実装する方法

HTMLでファイルや画像をdownloadする為の単純な実装はこんな感じだとおもいます。

html
1
<a href="ダウンロードファイルのパス" download="ダウンロードした時のファイル名">click</a>

ただ、この実装では画像がクロスドメインの場合はダウンロードできません。また、safariでは別タブが開き右クリックで画像を保存しないとダウンロードできなかったりします。

やりたかったこと

HTMLのimgタグで表示している画像をクリックすると、画像をダウンロードさせる。という仕様を実装した時に色々苦労したのでメモしておきます。処理の順序としては以下の順番で説明します。またTypeScript用の型も指定しておきます。

  • 画像URLからnew Image()する
  • クロスドメインを回避する方法
  • canvasにdrawImageしてjpegに変換してbase64にする
  • base64からBlobに変換してFileSaverを使ってダウンロードさせる
  • safariでダウンロードフォルダーに保存させるために使用

画像URLからnew Image()する

画像URLからHTMLImageElementインスタンスを作成します。

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
async load(imageURL) { 
  let imageSouce = await imageLoader(imageURL); 
}

imageLoader(imageURL) { 
  return new Promise((resolve, reject) => { 
    let image= new Image();
    image.crossOrigin = "anonymous";
    image.src = imageURL; 
    image.onload = function(){
      return resolve(image);
    }
  })
}

例えば画像URLがs3やakamaiサーバーにある場合、クロスドメインになると思います。それを回避するには下記の1行を追加することで回避することができます。

js
1
image.crossOrigin = "anonymous";

canvasにdrawImageしてjpegに変換してbase64にする

状況によっては png ではなく jpeg が必要だったりするかもしれません。その場合 canvas を使って jpeg に変換します。また、後述する FileSaverBlob にする必要があるので base64 に変換しておきます。

js
1
2
3
4
5
6
7
8
9
10
11
let canvas: 
HTMLCanvasElement = document.createElement("canvas"); 
let ctx: CanvasRenderingContext2D; 
let dataURL: string; let base64: string;

canvas.width = imageSource.naturalWidth; 
canvas.height = imageSource.naturalHeight; 
ctx = canvas.getContext("2d"); 
ctx.drawImage(imageSource, 0, 0); 
dataURL = canvas.toDataURL("image/jpeg"); 
base64 = dataURL.split(",")[1];

base64からBlobに変換してFileSaverを使ってダウンロードさせる

FileSaverを使うことによってsafariでもファイルをダウンロードさせることができます。 FileSaverbase64 から Blob に変換してから使用します。

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
base64ToBlob(base64) {
  let blob;
  let bin = atob(base64.replace(/^.\*,/, "")); 
  let buffer = new Uint8Array(bin.length); 
  for (let i = 0; i < bin.length; i++) { 
    buffer[i] = bin.charCodeAt(i);
  }
  // Blobを作成
  try { 
    blob = new Blob([buffer.buffer], { 
      type: "image/jpeg" 
    }); 
  } catch (e) { 
    return false; 
  } 
  return blob; 
}

let blob = this.base64ToBlob(base64); 
FileSaver.saveAs(blob, fileName);