風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

HTML5・A要素(リンク)のdownload属性に関する覚え書き

これもコピィ・ライター作成時に、

  • 動的に生成した画像をボタンをクリックしてダウンロード

する機能を実現する過程で、HTML5・A要素(リンク)の download 属性について調べたことに関する覚え書き。



HTML5・A要素(リンク)の download 属性とは


download HTML5


この属性は、ユーザがリンクをクリックするとリソースをローカルファイルとして保存することを促されるように、リソースをダウンロードするために使用されるハイパーリンクであることをページ作者が意図して記述します。属性に値が指定された場合、ユーザがリンクをクリックしたときに開く保存プロンプトの、デフォルトのファイル名として解釈します (もちろん、ユーザは実際にファイルを保存する前にファイル名を変更できます)。使用可能な値に制限はありません (ただし / および \ はアンダースコアに変換して、特定のパスヒントを防ぎます) が、多くのファイルシステムには、ファイル名に使用できない文字があることを考慮する必要があります。ブラウザがファイル名を調整するかもしれません。

補足:

  • この属性は、ユーザが簡単に JavaScript を使用するプログラムで生成されたコンテンツ (例えばオンラインのお絵かき Web アプリを使用して描いた画像) をダウンロードするため、blob: URL および data: URL とともに使用できます。
  • この属性で指定したものと異なるファイル名を Content-Disposition: HTTP ヘッダで与えている場合は、この属性より HTTP ヘッダが優先します。
  • この属性を指定するとともに Content-Disposition:inline を指定している場合、Firefox はファイル名と同様に Content-Disposition を優先しますが、Chrome は download 属性を優先します。
  • Firefox 20 では、この属性は同一生成元のリソースへのリンクにのみ受け入れられます。

a 要素 - HTML | MDN

とりあえず、Can I use... Support tables for HTML5, CSS3, etcで調べてみると、最新のFirefox・Chrome・Operaはサポートしている模様。
またIEは無いのか……と思ったが、代替手段(window.navigator.msSaveOrOpenBlob())でなんとかなりそうではあった。
Safari? えっと、知らない子ですね……。

実験とブラウザ毎の結果

  • [A] 画像ファイルを Data URL に変換したもの
  • [B] 画像ファイル(サイト内)へのリンク
  • [C] 画像ファイル(外部サイト)へのリンク

三種類の A(リンク)要素を用意し、それぞれに download 属性でファイル名を指定した場合のテストを行った。
IE用には別途、スクリプトで window.navigator.msSaveOrOpenBlob() を使った細工をしてある。

それぞれのリンクをクリック(タップ)した結果、以下のようになった。

Google Chrome
43.0.2357.134 m
Firefox
39.0
Opera
30.0.1835.125
IE11 Chrome for Android
43.0.2357.93
[A]
[B] ×
[C] × ×
  • ○:download属性で指定したファイル名でダウンロード
  • △:download属性は無視され、href属性を元にしたファイル名でダウンロード
  • ▲:download属性で指定したファイル名でダウンロード
    IE用に、リンクに onclick トリガを設定し、window.navigator.msSaveOrOpenBlob() を使った細工を入れている
  • ※:別窓(タブ)で画像が開く
    IE用に、リンクに onclick トリガを設定し、XMLHttpRequest で画像を取得させ、エラーが発生したら別窓(タブ)で開く細工を入れている
  • ×:hrefで指定された先にページ遷移

外部サイト上のファイルの場合の動作はセキュリティがらみの制限なのだろうと推測されるが、Chrome for Android でサイト内ファイルへのリンクまでページ遷移してしまうのは謎。なぜ PC 版と動作を変える必要があったのか?

関連する処理など

canvas 要素の Data URL への変換

参考:toDataURL() メソッド - Canvasリファレンス - HTML5.JP

var dataURL = canvas.toDataURL(type); // canvas は HTML5 Canvas の DOM要素、typeは画像の種別('image/png', 'image/jpeg'等)
データの Blob オブジェクトへの変換

参考:Blob - Web API インターフェイス | MDN

var blobObject = new Blob([data], {'type' : mimeType}); // data は元データ、mimeType は元データの MIMEタイプ('image/svg+xml'等)
Data URL の Blob オブジェクトへの変換

参考:Canvas に描いた画像を png などの形式の Blob に変換する方法: Tender Surrender

function make_blob_from_dataurl(dataurl) {
    if (!dataurl.match(/^data:(.*?);base64,(.*)$/)) {
        return null;
    }
    var type = RegExp.$1, base64 = RegExp.$2;
    
    var bin = atob(base64), bin_length = bin.length;
    var buffer = new Uint8Array(bin_length);
    for (var ci=0; ci < bin_length; ci++) {
        buffer[ci] = bin.charCodeAt(ci);
    }  
    var blobObject = new Blob([buffer.buffer], {type: type});
    
    return blobObject;
}
ファイルを Blob オブジェクトとして取得

参考:バイナリデータの送信と受信 - XMLHttpRequest | MDN

var xhrObject = new XMLHttpRequest();
xhrObject.open('GET', url, true);
xhrObject.responseType = 'blob';
xhrObject.onload = function(event) {
    var blobObject = xhrObject.response;
    // ◆ 以下、blobObject を用いた処理を記述
};
xhrObject.onerror = function(event) {
    //※(許可されていない)外部サイトのファイルを取得しようとすると以下のようなエラーが発生(IEの例)
    //  SEC7118: (取得しようとしたURL) の XMLHttpRequest には Cross Origin Resource Sharing (CORS) が必要です。
    //  ファイル: (元ファイル)
    //  SEC7120: 元の http://(元ドメイン) が Access-Control-Allow-Origin ヘッダーに見つかりません。
    //  ファイル: (元ファイル)
    //  SCRIPT7002: XMLHttpRequest: ネットワーク エラー 0x80070005, アクセスが拒否されました。
    
    window.open(url);   // 次善の策として、例えばポップアップで開く
};
xhrObject.send();
Blob オブジェクトの Blob URL への変換

参考:window.URL.createObjectURL - Web API インターフェイス | MDN

var blobURL = (window.URL || window.webkitURL).createObjectURL(blobObject);
Blob オブジェクトをファイルとして保存したり開いたりするイベントの発生(IE10+)

参考:msSaveOrOpenBlob method (Internet Explorer)

window.navigator.msSaveOrOpenBlob(blobObject, filename);

その他気付いたことなど

  • A要素(リンク)の場合、jQuery によってクリックイベントを発生( .click() や .trigger('click'))させても、ダウンロードやページ遷移は行われない。
    これらを発生させたい場合には、MouseEvent を作成するか、DOM の .click() をコールする。