- 記事一覧 >
- ブログ記事
iframeを使ったダウンロードでChromeの場合、完了イベントを検知できない
JavaScriptでiframeを使って、ダウンロードをすると、準備完了時にイベントが発生し、検知できます。ただし、Chromeではイベントが発生しないため、検知できず、Cookieを使うといった工夫が必要です。
大前提として、ダウンロード中に他の操作をされたら困るという要件があり、その間画面をブロックする仕様とします。正確には、ダウンロード中と言うか、ダウンロード準備中です。ダウンロードが始まれば、あとはブラウザが裏でダウンロードします。
準備完了とは、ブラウザの保存ダイアログが表示されたり、ブラウザの制御下に入るタイミングのことです。
↑
マウスオーバーでアクションが起きる緑、青、赤、グレーのバーは、ブロックされていることを示すためで、意味は有りません。
【検証環境】
Windows10 Pro 64bit
IE11
Edge 40.15063.0.0(非Chromium)
Edge(Chromium) 93.0.961.38
Chrome 93.0.4577.63
Firefox 78.13.0esr
Windows版Safari 5.0.5
CentOS Linux release 8.3.2011
Apache/2.4.37
PHP 7.4.6
まとめ
結論から書きますと、iframeを使ったダウンロードでChromeの場合、準備完了イベントを検知できない問題は、Cookieを使うと解決します。
iframeのイベントを使ってダウンロード準備完了を検知
ブラウザ | ダウンロード準備完了検知 |
---|---|
IE11 | ○ |
Edge(非Chromium) | ○ |
Edge(Chromium) | × |
Chrome | × |
Firefox | ○ |
Windows版Safari | ○ |
ChromeとEdge(Chromium)は、ダウンロード準備完了を検知できません。
↓
Cookieを使ってダウンロード準備完了を検知
ブラウザ | ダウンロード準備完了検知 |
---|---|
IE11 | ○ |
Edge(非Chromium) | ○ |
Edge(Chromium) | ○ |
Chrome | ○ |
Firefox | ○ |
Windows版Safari | ○ |
Cookieを使ってダウンロード準備完了を検知できます。さらに、イベントを検知するわけでは無いため、iframeを使う必要が無くなります。
アンカーリンク(aタグ)でダウンロードしても目的(画面ブロック解除)は達成します。
イベントの検知
各ブラウザで以下のようにダウンロード準備完了イベントの検知を試みた結果です。<iframe src="/download.php" onReadyStateChange="readyStateChange()" onLoad="loaded()"></iframe>
iframeのイベントを使ってダウンロード準備完了を検知
ブラウザ | ダウンロード準備完了イベント |
---|---|
IE11 | onReadyStateChange |
Edge(非Chromium) | 無し |
Edge(Chromium) | 無し |
Chrome | 無し |
Firefox | onLoad |
Windows版Safari | 無し |
単純にイベントだけではカバーしきれないため、document.getElementById('download').contentDocument.readyState;
の値を調べます。
↓
ブラウザ | readyState |
---|---|
IE11 | loading → interactive |
Edge(非Chromium) | loading → interactive |
Edge(Chromium) | completeのまま |
Chrome | completeのまま |
Firefox | uninitialized → complete |
Windows版Safari | loading → complete |
ダウンロード準備中→ダウンロード準備完了 で、Edge(非Chromium)は、interactive、Safariは、completeを見ると分かります。
ChromeとEdge(Chromium)は、この方法でもずっとcompleteのため、ダウンロード準備完了を検知できません。
Cookieで検知のしくみ
以下のようにデータ送出と同時にCookieが送られるようにして、Cookieの有無でダウンロード準備完了を検知します。
アンカーリンクでのダウンロード
既存システムの改修などでもなく、特に縛りが無い場合、iframeである必要は無くなります。 アンカーリンク(aタグ)にして、クッキー検知で統一すれば良いです。
Cookieを使ってダウンロード準備完了を検知
ブラウザ | ダウンロード準備完了検知 |
---|---|
IE11 | ○ |
Edge(非Chromium) | ○ |
Edge(Chromium) | ○ |
Chrome | ○ |
Firefox | ○ |
Windows版Safari | ○ |
動画gifのソースコード
画面側ソースコード:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Download via iframe</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" />
<script>
var intervalID;
function readyStateChange() {
$.modal.close();
}
window.onload = function () {
$("#anchorlink").on("click", function () {
$("#blockui").modal({
escapeClose: false,
clickClose: false,
showClose: false
});
document.cookie = "downloaded=; path=/; expires=" + new Date(0).toUTCString() + ";";//Thu, 01 Jan 1970 00:00:00 GMT
intervalID = setInterval(function () {
if (document.cookie.indexOf("downloaded") >= 0) {
document.cookie = "downloaded=; path=/; expires=" + new Date(0).toUTCString() + ";";
clearInterval(intervalID);
$.modal.close();
}
}, 500);
});
$("#btn").on("click", function () {
$("#blockui").modal({
escapeClose: false,
clickClose: false,
showClose: false
});
if (!!window.chrome && /Chrome/.test(navigator.userAgent)) {//Chrome
document.cookie = "downloaded=; path=/; expires=" + new Date(0).toUTCString() + ";";
var html = '<iframe id="download" src="/download.php">';
document.getElementById("frame").innerHTML = html;
intervalID = setInterval(function () {
if (document.cookie.indexOf("downloaded") >= 0) {
document.cookie = "downloaded=; path=/; expires=" + new Date(0).toUTCString() + ";";
clearInterval(intervalID);
$.modal.close();
}
}, 500);
} else if (/*@cc_on!@*/ false || !!document.documentMode) {//IE11
var html = '<iframe id="download" src="/download.php" onReadyStateChange="readyStateChange()" width=0 height=0></iframe>';
document.getElementById("frame").innerHTML = html;
} else if (/constructor/i.test(window.HTMLElement) ||
(function (p) {
return p.toString() === '[object SafariRemoteNotification]';
})(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)) ||//Safari
(!(/*@cc_on!@*/ false || !!document.documentMode) && !!window.StyleMedia)) {//Edge
var html = '<iframe id="download" src="/download.php">';
document.getElementById("frame").innerHTML = html;
intervalID = setInterval("timer_event()", 200);
} else {
var iframe = document.createElement("iframe");
iframe.src = "/download.php";
if (document.all) {
iframe.onreadystatechange = function () {
switch (this.readyState) {
case "complete":
case "interactive":
$.modal.close();
this.onreadystatechange = null;
}
}
} else {
iframe.onload = function () {
$.modal.close();
}
}
document.getElementById("frame").appendChild(iframe);
}
});
}
function timer_event() {
var iframe = document.getElementById("download");
var status;
status = iframe.contentDocument.readyState;
if (status == "complete" || status == "interactive") {
clearInterval(intervalID);
$.modal.close();
}
}
</script>
<style>
html {
font-family: ヒラギノ角ゴ Pro, Hiragino Kaku Gothic Pro, メイリオ, Meiryo, Osaka, MS Pゴシック, MS PGothic, sans-serif;
}
#sidenav a {position: absolute; left: -80px; transition: 0.3s; padding: 15px; width: 100px; text-decoration: none; font-size: 20px; color: white; border-radius: 0 5px 5px 0;}
#sidenav a:hover {left: 0;}
#about {top: 20px; background-color: #04AA6D;}
#blog {top: 80px; background-color: #2196F3;}
#projects {top: 140px; background-color: #f44336;}
#contact {top: 200px; background-color: #555}
#btn {position: absolute; top: 300px; background-color: DodgerBlue; border: none; color: white; padding: 12px 30px; cursor: pointer; font-size: 20px;}
#btn:hover {background-color: RoyalBlue;}
#blockui>p {text-align: center}
#anchorlink {position: absolute; top: 400px;}
</style>
</head>
<body>
<div id="sidenav" class="sidenav">
<a href="#" id="about">About</a>
<a href="#" id="blog">Blog</a>
<a href="#" id="projects">Projects</a>
<a href="#" id="contact">Contact</a>
</div>
<button id="btn"><i class=" fa fa-download"></i> Download</button>
<div id="blockui" style="display: none;">
<p>Please wait</p>
</div>
<div id="anchorlink"><a href="/download.php">Download</a></div>
<div id="frame" style="display: none;"></div>
</body>
</html>
サーバー側ソースコード(php):
<?php
sleep(2);//何らかの時間がかかる処理
$file = 'test.dat';
setcookie('downloaded', 'true', time() + 3600, '/');
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Length: '.filesize($file));
mb_http_output('pass');
readfile($file);
echo filesize($file);
exit;
Cookieが残ったままになるとまずいため、expires(クッキーの有効期限)を一時間後に設定しています。※念のための処置で、ダウンロード準備に一時間かかると検知できなくなるわけではありません。
ajaxを使う場合
ajaxを使ってダウンロードする方法を試しました。
参考:Handle file download from ajax post(stackoverflow)
・ダウンロード準備完了ではなく、本当にダウンロード完了の時までブロックになりました。
・ダウンロード対象の容量が大きいと動作しないようです。
・Windows版Safariは動きませんでした。
この例では、jquery.blockUI.js
で画面をブロックしています。$(document).ajaxStop(stopWaiting);
のところで画面ブロックを解除しています。
【 ajaxStop 】
ajaxリクエストの送信終了時に実行される関数を引数に指定します。
ajaxを使ってダウンロードのサンプルコード:
※php側は同じ実装とします。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Download from ajax</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.js"></script>
<script>
function startWaiting() {
$.blockUI({
message: 'Please wait...',
css: {
width: '250px',
padding: '30px 0px',
border: 'none',
cursor: 'wait'
}
});
}
function stopWaiting() {
$.unblockUI();
}
$(document).ajaxStop(stopWaiting);
window.onload = function () {
$('#btn').on('click', function () {
startWaiting();
$.ajax({
type: 'GET',
url: '/download.php',
xhrFields: {
responseType: 'blob' // to avoid binary data being mangled on charset conversion
},
success: function (blob, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
}
});
});
}
</script>
<style>
html {font-family: ヒラギノ角ゴ Pro, Hiragino Kaku Gothic Pro, メイリオ, Meiryo, Osaka, MS Pゴシック, MS PGothic, sans-serif;}
#btn {position: absolute; top: 100px; background-color: DodgerBlue; border: none; color: white; padding: 12px 30px; cursor: pointer; font-size: 20px;}
#btn:hover {background-color: RoyalBlue;}
</style>
</head>
<body>
<button id="btn"><i class=" fa fa-download"></i> Download</button>
</body>
</html>
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。