javascript 检测 iframe 何时加载
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23766086/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detect when an iframe is loaded
提问by 3rgo
I'm using an <iframe>
(I know, I know, ...) in my app (single-page application with ExtJS 4.2) to do file downloads because they contain lots of data and can take a while to generate the Excel file (we're talking anything from 20 seconds to 20 minutes depending on the parameters).
我<iframe>
在我的应用程序(带有 ExtJS 4.2 的单页应用程序)中使用(我知道,我知道,...)来进行文件下载,因为它们包含大量数据并且可能需要一段时间来生成 Excel 文件(我们正在谈论从 20 秒到 20 分钟的任何内容,具体取决于参数)。
The current state of things is : when the user clicks the download button, he is "redirected" by Javascript (window.location.href = xxx
) to the page doing the export, but since it's done in PHP, and no headers are sent, the browser continuously loads the page, until the file is downloaded. But it's not very user-friendly, because nothing shows him whether it's still loading, done (except the file download), or failed (which causes the page to actually redirect, potentially making him lose the work he was doing).
当前的状态是:当用户点击下载按钮时,他被 Javascript ( window.location.href = xxx
) “重定向”到执行导出的页面,但由于它是在 PHP 中完成的,并且没有发送标题,浏览器不断加载页面, 直到文件下载完毕。但它不是很用户友好,因为没有任何东西向他显示它是否仍在加载、完成(除了文件下载)或失败(这会导致页面实际重定向,可能使他失去他正在做的工作)。
So I created a small non-modal window docked in the bottom right corner that contains the iframe as well as a small message to reassure the user. What I need is to be able to detect when it's loaded and be able to differenciate 2 cases :
因此,我创建了一个停靠在右下角的小型非模态窗口,其中包含 iframe 以及一条让用户放心的小消息。我需要的是能够检测何时加载并能够区分两种情况:
- No data : OK => Close window
- Text data : Error message => Display message to user + Close window
- 无数据:确定 => 关闭窗口
- 文本数据:错误消息 => 向用户显示消息 + 关闭窗口
But I tried all 4 events (W3Schools doc) and none is everfired. I could at least understand that if it's not HTML data returned, it may not be able to fire the event, but even if I force an error to return text data, it's not fired.
但是,我尝试了所有4个事件(W3Schools的文档),并没有被永远开除。我至少可以理解,如果返回的不是 HTML 数据,它可能无法触发该事件,但即使我强制错误返回文本数据,它也不会被触发。
If anyone know of a solution for this, or an alternative system that may fit here, I'm all ears ! Thanks !
如果有人知道此问题的解决方案,或可能适合此处的替代系统,我会全力以赴!谢谢 !
EDIT : Added iframe code. The idea is to get a better way to close it than a setTimeout
.
编辑:添加 iframe 代码。这个想法是获得比setTimeout
.
var url = 'http://mywebsite.com/my_export_route';
var ifr = $('<iframe class="dl-frame" src="'+url+'" width="0" height="0" frameborder="0"></iframe>');
ifr.appendTo($('body'));
setTimeout(function() {
$('.dl-frame').remove();
}, 3000);
回答by Anton Melnikov
I wonder if it would require some significant changes in both frontend and backend code, but have you considered using AJAX? The workflow would be something like this: user sends AJAX request to start file generating and frontend constantly polls it's status from the server, when it's done - show a download link to the user. I believe that workflow would be more straightforward.
我想知道是否需要对前端和后端代码进行一些重大更改,但您是否考虑过使用 AJAX?工作流程将是这样的:用户发送 AJAX 请求以开始文件生成,前端不断从服务器轮询它的状态,完成后 - 向用户显示下载链接。我相信工作流程会更直接。
Well, you could also try this trick. In parent window create a callback function for the iframe's complete loading myOnLoadCallback
, then call it from the iframe with parent.myOnLoadCallback()
. But you would still have to use setTimeout
to handle server errors/connection timeouts.
嗯,你也可以试试这个技巧。在父窗口中为 iframe 的完整加载创建一个回调函数myOnLoadCallback
,然后从 iframe 中调用它parent.myOnLoadCallback()
。但是您仍然必须使用setTimeout
来处理服务器错误/连接超时。
And one last thing - how did you tried to catch iframe's events? Maybe it something browser-related. Have you tried setting event callbacks in HTML attributes directly? Like
最后一件事 - 你是如何尝试捕捉 iframe 的事件的?也许它与浏览器相关。您是否尝试过直接在 HTML 属性中设置事件回调?喜欢
<iframe onload="done()" onerror="fail()"></iframe>
That's a bad practice, I know, but sometimes job need to be done fast, eh?
我知道这是一种不好的做法,但有时需要快速完成工作,是吗?
UPDATEWell, I'm afraid you have to spend a long and painful day with a JS debugger. load
event shouldwork. I still have some suggestions, though:
更新好吧,恐怕您不得不在 JS 调试器上度过漫长而痛苦的一天。load
事件应该工作。不过我还是有一些建议:
1) Try to set event listener before setting element's src
. Maybe onload
event fires so fast that it slips between creating element and setting event's callback
1) 在设置元素的src
. 也许onload
事件触发得太快以至于它在创建元素和设置事件的回调之间滑动
2) At the same time try to check if your server code plays nicely with iframes. I have made a simple test which attempts to download a PDF from Dropbox, try to replace my URL with your backed route's.
2) 同时尝试检查您的服务器代码是否与 iframe 配合良好。我做了一个简单的测试,尝试从 Dropbox 下载 PDF,尝试用你支持的路由替换我的 URL。
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<iframe id="book"></iframe>
<button id="go">Request downloads!</button>
<script>
var bookUrl = 'https://www.dropbox.com/s/j4o7tw09lwncqa6/thinkpython.pdf';
$('#book').on('load', function(){
console.log('WOOT!', arguments);
});
$('#go').on('click', function(){
$('#book').attr('src', bookUrl);
});
</script>
UPDATE 2
更新 2
3) Also, look at the Network tab of your browser's debugger, what happens when you set src
to the iframe, it should show request and server's response with headers.
3) 另外,查看浏览器调试器的 Network 选项卡,当您设置src
iframe时会发生什么,它应该显示请求和服务器的响应以及标头。
回答by Vitor Canova
I've tried with jQuery and it worked just fine as you can see in this post.
我已经尝试过 jQuery,它运行得很好,正如你在这篇文章中看到的那样。
I made a working example here.
我在这里做了一个工作示例。
It's basically this:
基本上是这样的:
<iframe src="http://www.example.com" id="myFrame"></iframe>
And the code:
和代码:
function test() {
alert('iframe loaded');
}
$('#myFrame').load(test);
Tested on IE11.
在 IE11 上测试。
回答by mfirdaus
I guess I'll give a more hacky alternative to the more proper ways of doing it that the others have posted. If you have control over the PHP download script, perhaps you can just simply output javascript when the download is complete. Or perhaps redirect to a html page that runs javascript. The javascript run, can then try to call something in the parent frame. What will work depends if your app runs in the same domain or not
我想我会为其他人发布的更正确的做法提供一个更hacky的替代方案。如果您可以控制 PHP 下载脚本,也许您可以在下载完成时简单地输出 javascript。或者可能重定向到运行 javascript 的 html 页面。javascript 运行,然后可以尝试调用父框架中的某些内容。什么会起作用取决于您的应用程序是否在同一域中运行
Same domain
同域
Same domain frame can just use frame javascript objects to reference each other. so it could be something like, in your single page application you can have something like
同域框架只能使用框架 javascript 对象来相互引用。所以它可能是这样的,在你的单页应用程序中,你可以有类似的东西
window.downloadHasFinished=function(str){ //Global pollution. More unique name?
//code to be run when download has finished
}
And for your download php script, you can have it output this html+javascript when it's done
对于你下载的 php 脚本,你可以让它在完成后输出这个 html+javascript
<script>
if(parent && parent.downloadHasFinished)
parent.downloadHasFinished("if you want to pass a data. maybe export url?")
</script>
- Demo jsfiddle(Must run in fullscreen as the frames have different domain)
- Parent jsfiddle
- Child jsfiddle
- 演示 jsfiddle(必须全屏运行,因为框架具有不同的域)
- 父 jsfiddle
- 儿童 jsfiddle
Different Domains
不同的域
For different domains, We can use postMessage
. So in your single page application it will be something like
对于不同的域,我们可以使用postMessage
. 因此,在您的单页应用程序中,它将类似于
$(window).on("message",function(e){
var e=e.originalEvent
if(e.origin=="http://downloadphp.anotherdomain.com"){ //for security
var message=e.data //data passed if any
//code to be run when download has finished
}
});
and in your php download script you can have it output this html+javascript
在你的 php 下载脚本中,你可以让它输出这个 html+javascript
<script>
parent.postMessage("if you want to pass data",
"http://downloadphp.anotherdomain.com");
</script>
Conclusion
结论
Honestly, if the other answers work, you should probably use those. I just thought this was an interesting alternative so I posted it up.
老实说,如果其他答案有效,您可能应该使用它们。我只是觉得这是一个有趣的选择,所以我把它贴出来了。
回答by guest271314
Try this (pattern)
试试这个(模式)
$(function () {
var session = function (url, filename) {
// `url` : URL of resource
// `filename` : `filename` for resource (optional)
var iframe = $("<iframe>", {
"class": "dl-frame",
"width": "150px",
"height": "150px",
"target": "_top"
})
// `iframe` `load` `event`
.one("load", function (e) {
$(e.target)
.contents()
.find("html")
.html("<html><body><div>"
+ $(e.target)[0].nodeName
+ " loaded" + "</div><br /></body></html>");
alert($(e.target)[0].nodeName
+ " loaded" + "\nClick link to download file");
return false
});
var _session = $.when($(iframe).appendTo("body"));
_session.then(function (data) {
var link = $("<a>", {
"id": "file",
"target": "_top",
"tabindex": "1",
"href": url,
"download": url,
"html": "Click to start {filename} download"
});
$(data)
.contents()
.find("body")
.append($(link))
.addBack()
.find("#file")
.attr("download", function (_, o) {
return (filename || o)
})
.html(function (_, o) {
return o.replace(/{filename}/,
(filename || $(this).attr("download")))
})
});
_session.always(function (data) {
$(data)
.contents()
.find("a#file")
.focus()
// start 6 second `download` `session`,
// on `link` `click`
.one("click", function (e) {
var timer = 6;
var t = setInterval(function () {
$(data)
.contents()
.find("div")
// `session` notifications
.html("Download session started at "
+ new Date() + "\n" + --timer);
}, 1000);
setTimeout(function () {
clearInterval(t);
$(data).replaceWith("<span class=session-notification>"
+ "Download session complete at\n"
+ new Date()
+ "</span><br class=session-notification />"
+ "<a class=session-restart href=#>"
+ "Restart download session</a>");
if ($("body *").is(".session-restart")) {
// start new `session`,
// on `.session-restart` `click`
$(".session-restart")
.on("click", function () {
$(".session-restart, .session-notification")
.remove()
// restart `session` (optional),
// or, other `session` `complete` `callback`
&& session(url, filename ? filename : null)
})
};
}, 6000);
});
});
};
// usage
session("http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf", "ECMA_JS.pdf")
});
回答by Ross Bush
You can use the following script. It comes from a project of mine.
您可以使用以下脚本。它来自我的一个项目。
$("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");
回答by Chrysi
Maybe you should use
也许你应该使用
$($('.dl-frame')[0].contentWindow.document).ready(function () {...})
回答by xiidea
You can use thislibrary. The code snippet for you purpose would be something like:
你可以使用这个库。用于您的代码片段将类似于:
window.onload = function () {
rajax_obj = new Rajax('',
{
action : 'http://mywebsite.com/my_export_route',
onComplete : function(response) {
//This will only called if you have returned any response
// instead of file from your export script
// In your case 2
// Text data : Error message => Display message to user
}
});
}
Then you can call rajax_obj.post()
on your download link click.
然后你可以调用rajax_obj.post()
你的下载链接点击。
<a href="javascript:rajax_obj.post()">Download</a>
NB: You should add some header to your PHP script so it force file download
注意:您应该在 PHP 脚本中添加一些标头,以便强制下载文件
header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');
回答by Scott
In regards to your comment about to get a better way to close it instead of setTimeout. You could use jQuery fadeOut option or any of the transitions and in the 'complete' callback remove the element. Below is an example you can dump right into a fiddle and only need to reference jQuery.
关于您关于获得更好的关闭方法而不是 setTimeout 的评论。您可以使用 jQuery 淡出选项或任何转换,并在“完成”回调中删除元素。下面是一个示例,您可以直接将其转储到小提琴中,并且只需要引用 jQuery。
I also wrapped inside listener for 'load' event to not do the fade until the iFrame has been loaded as question originally was asking.
我还将“加载”事件的侦听器包裹在内部,以便在 iFrame 已按照最初提出的问题加载之前不执行淡入淡出。
// plugin your URL here
var url = 'http://jquery.com';
// create the iFrame, set attrs, and append to body
var ifr = $("<iframe>")
.attr({
"src": url,
"width": 300,
"height": 100,
"frameborder": 0
})
.addClass("dl-frame")
.appendTo($('body'))
;
// log to show its part of DOM
console.log($(".dl-frame").length + " items found");
// create listener for load
ifr.one('load', function() {
console.log('iframe is loaded');
// call $ fadeOut to fade the iframe
ifr.fadeOut(3000, function() {
// remove iframe when fadeout is complete
ifr.remove();
// log after, should no longer exist in DOM
console.log($(".dl-frame").length + " items found");
});
});
回答by Onur Y?ld?r?m
Try this:
Note: You should be on the same domain.
试试这个:
注意:你应该在同一个域中。
var url = 'http://mywebsite.com/my_export_route',
iFrameElem = $('body')
.append('<iframe class="dl-frame" src="' + url + '" width="0" height="0" frameborder="0"></iframe>')
.find('.dl-frame').get(0),
iDoc = iFrameElem.contentDocument || iFrameElem.contentWindow.document;
$(iDoc).ready(function (event) {
console.log('iframe ready!');
// do stuff here
});
回答by Erik Simonic
If you are doing a file download from a iframe the load event wont fire :) I was doing this a week ago. The only solution to this problem is to call a download proxy script with a tag and then return that tag trough a cookie then the file is loaded. min while yo need to have a setInterval on the page witch will watch for that specific cookie.
如果您正在从 iframe 下载文件,则加载事件不会触发 :) 我一周前就这样做了。此问题的唯一解决方案是调用带有标签的下载代理脚本,然后通过 cookie 返回该标签,然后加载文件。分钟,而您需要在页面上设置 setInterval 女巫将监视该特定 cookie。
// Jst to clearyfy
// Jst 清除
var token = new Date().getTime(); // ticks
$('<iframe>',{src:"yourproxy?file=somefile.file&token="+token}).appendTo('body');
var timers = [];
timers[timers.length+1] = setInterval(function(){
var _index = timers.length+1;
var cookie = $.cooke(token);
if(typeof cookie != "undefined"){
// File has been downloaded
$.removeCookie(token);
clearInterval(_index);
}
},400);
in your proxy script add the cookie with the name set to the string sent bay the token url parameter.
在您的代理脚本中,将名称设置为字符串发送的 cookie 添加到令牌 url 参数中。