【问题标题】:Detect when an iframe is loaded检测何时加载 iframe
【发布时间】:2014-07-09 02:04:06
【问题描述】:

我在我的应用程序(带有 ExtJS 4.2 的单页应用程序)中使用 <iframe>(我知道,我知道,...)进行文件下载,因为它们包含大量数据并且可能需要一段时间才能下载生成 Excel 文件(根据参数,我们所说的时间从 20 秒到 20 分钟不等)。

目前的情况是:当用户单击下载按钮时,他被 Javascript (window.location.href = xxx) “重定向”到执行导出的页面,但由于它是在 PHP 中完成的,并且没有发送任何标题,浏览器不断加载页面,直到文件被下载。但它对用户不是很友好,因为没有任何东西可以告诉他它是仍在加载、完成(除了文件下载)还是失败(这会导致页面实际重定向,可能会让他失去他正在做的工作)。

因此,我创建了一个停靠在右下角的小型非模态窗口,其中包含 iframe 以及一条小消息以安抚用户。我需要的是能够检测到它何时加载并能够区分两种情况:

  • 没有数据:OK => 关闭窗口
  • 文本数据:错误消息 => 向用户显示消息 + 关闭窗口

但我尝试了所有 4 个事件 (W3Schools doc),但没有一个曾经被触发。我至少可以理解,如果不是返回的 HTML 数据,它可能无法触发事件,但即使我强制错误返回文本数据,它也不会被触发。

如果有人知道这个问题的解决方案,或者可能适合这里的替代系统,我会全力以赴!谢谢!

编辑:添加 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);

【问题讨论】:

  • 你可以使用jQuery.load()
  • 请出示您的代码
  • 编辑了我的帖子以添加一些代码
  • 这是检测 iframe 负载所需的 iframe.on('load',callbackFunction)
  • 尝试将 onload = "myFunction()" 添加到您的 iframe 标记并定义该函数

标签: javascript iframe


【解决方案1】:

也许你应该使用

$($('.dl-frame')[0].contentWindow.document).ready(function () {...})

【讨论】:

  • 万岁,这实际上对我有用!如果请求是下载文件,仍然会触发(即当 onload 不起作用时它会起作用。)
【解决方案2】:

我已经尝试过使用 jQuery,它工作得很好,正如你在这篇文章中看到的那样。

我做了一个工作示例here

基本上是这样的:

<iframe src="http://www.example.com" id="myFrame"></iframe>

还有代码:

function test() {
    alert('iframe loaded');
}

$('#myFrame').load(test);

在 IE11 上测试。

【讨论】:

  • 这显然适用于简单的 HTML 内容,但正如我在 OP 中所说,我正在加载的 url 不是 HTML 内容,它是一个文档(主要是 Excel 文件,但它可以是 PDF 或字)
  • PDF 文档也会触发警报! jsfiddle.net/Yz6NP/1 6 秒后将触发警报!
  • @Squ36 你说你正在生成这个文件。您是否有我们可以测试此行为的 URL?据我所知,iframe 触发加载事件是因为它包含的 html。如果你没有 html 来加载你的问题标题是完全错误的。应该是“如何在浏览器中静默开始下载文件”之类的。我认为弹出窗口可以比 iframe 更接近你想要的。
  • @VitorCanova :抱歉,我无法为您提供测试的 URL,因为它是一个 Intranet 应用程序。我知道还有其他方法可以进行静默文件下载,但 iframe 是我们发现的最好的,因为弹出窗口无法关闭,如果出现问题,简单的重定向实际上会重定向用户
【解决方案3】:

我想知道它是否需要对前端和后端代码进行一些重大更改,但是您是否考虑过使用 AJAX?工作流程是这样的:用户发送 AJAX 请求以开始文件生成,前端不断地从服务器轮询它的状态,当它完成时 - 向用户显示下载链接。我相信工作流程会更简单。

嗯,你也可以试试这个技巧。在父窗口中为 iframe 的完整加载创建一个回调函数myOnLoadCallback,然后在 iframe 中使用parent.myOnLoadCallback() 调用它。但是您仍然必须使用setTimeout 来处理服务器错误/连接超时。

最后一件事 - 您是如何尝试捕获 iframe 的事件的?也许它与浏览器相关。您是否尝试过直接在 HTML 属性中设置事件回调?喜欢

<iframe onload="done()" onerror="fail()"></iframe>

我知道这是一种不好的做法,但有时需要快速完成工作,嗯?

更新 好吧,恐怕你不得不用一个 JS 调试器来度过漫长而痛苦的一天。 load 事件应该起作用。不过,我仍然有一些建议:

1) 尝试在设置元素的src 之前设置事件监听器。也许onload 事件触发得太快以至于它在创建元素和设置事件回调之间滑动

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>

更新 2

3) 另外,请查看浏览器调试器的“网络”选项卡,当您将 src 设置为 iframe 时会发生什么情况,它应该显示请求和服务器的响应以及标头。

【讨论】:

  • 长轮询有点像我们之前已经拥有的:用户点击链接,它向服务器发送 AJAX 请求,服务器生成文件并为 AJAX 返回指向该文件的链接称呼。然后在客户端,我们打开一个带有该链接的小窗口。一切都很好,但是窗口不会自行关闭,所以我认为使用 iframe 会更好。我会查看您的提示并回复您
  • 我尝试将简单的alert('test'); 放在onloadonerror 属性中,但没有通过... 至于从iframe 中调用回调函数,这是不可能的,因为它不是我正在加载的 HTML 内容,它是一个文档(Excel、Word、PDF)
  • 难道您不只是将链接附加到您的 UI(在 ajax 完成后)而不是打开一个新窗口(就像堆栈溢出用于警报一样)吗?您也可以使用网络套接字或eventsource 代替长轮询。
  • 你不需要让用户点击链接。一旦 Ajax 通过作为响应发送的 url 生成文件,您可以创建一个隐藏的表单或链接,自动提交/自动点击。在这里我使用了那个方法并且效果很好。
  • @Squ36 是的,可能有两个这样的事件,我在测试我的示例时看到了自己,虽然它依赖于浏览器,但 Firefox 的行为与 Chrome 没有。第一个在加载空 src 时触发,第二个在加载文档时触发。您可以通过事件数据或仅通过 iframe 的 src 过滤它们。你的后端呢?当您按下按钮时,console.log 是否显示了什么?
【解决方案4】:

您可以使用this 库。为您准备的代码 sn-p 类似于:

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
        }
    });
}

然后您可以在点击下载链接时拨打rajax_obj.post()

<a href="javascript:rajax_obj.post()">Download</a>

注意:您应该在 PHP 脚本中添加一些标头,以便强制下载文件

header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');

【讨论】:

    【解决方案5】:

    关于您的评论将获得更好的方法来关闭它而不是 setTimeout。您可以使用 jQuery fadeOut 选项或任何转换,并在“完成”回调中删除元素。下面是一个示例,您可以直接转储到小提琴中,并且只需要引用 jQuery。

    我还将“加载”事件的侦听器包裹在侦听器中,以便在 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");
        });  
    }); 
    

    【讨论】:

    • 顺便说一句,除了我发布的代码之外,通过阅读您想要完成的内容,您始终可以使用诸如 alertify 之类的库。我已经取得了巨大的成功,听起来你想要完成的事情可以很好地与 alertify 一起使用?查看默认通知部分。只是另一个建议。
    • 感谢有关 alertify 的提示。在这种特殊情况下,不需要它,因为该项目使用 ExtJS,因此制作通知样式的窗口很容易,并且其设计保持一致。至于您的代码建议,我不使用 jQuery,并且 ExtJS 的加载事件似乎没有触发。我会在星期一更广泛地检查它,并让你知道
    • 您使用的是什么版本的 ExtJS?你可以在没有 jQuery 的情况下完成同样的事情。当然,正如您已经看到的 setTimeout 也可以。根据 ExtJS 的版本,我曾经在 ExtJS 中遇到过 iFrame 加载事件的问题,但这是前一段时间了。
    【解决方案6】:

    试试这个:
    注意:您应该在同一个域中。

    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
    });
    

    【讨论】:

      【解决方案7】:

      如果您正在从 iframe 下载文件,则加载事件不会触发 :) 我一周前正在这样做。此问题的唯一解决方案是调用带有标签的下载代理脚本,然后通过 cookie 返回该标签,然后加载文件。分钟,而你需要在页面上有一个 setInterval,女巫会监视那个特定的 cookie。

      // Jst to clearyfy

      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);
      

      在您的代理脚本中,将名称设置为字符串的 cookie 添加到发送的令牌 url 参数中。

      【讨论】:

        【解决方案8】:

        如果您在服务器中控制生成 excel 的脚本或您发送到 iframe 的任何内容,为什么不放置一个 UID 标志并将其存储在值为 0 的会话中,所以...当创建 iframe 并且服务器脚本是调用只是将 UID 标志设置为 1,当脚本完成时(将加载 iframe)只需将其设置为 2。

        然后你只需要一个计时器和一个定期的 AJAX 调用服务器来检查 UID 标志...如果它设置为 0 则进程不会启动,如果它是 1 则正在创建文件,最后如果它是 2该过程已结束。

        你怎么看?如果您需要有关此方法的更多信息,请询问。

        【讨论】:

          【解决方案9】:

          您所说的可以使用$(iframe).load(function() {...}); 为图像和其他媒体格式完成

          对于 PDF 文件或其他富媒体,您可以使用以下库: http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/

          注意:您将需要 JQuery UI

          【讨论】:

            【解决方案10】:

            您可以使用以下脚本。它来自我的一个项目。

            $("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");
            

            【讨论】:

              【解决方案11】:

              我想我会给其他人发布的更正确的做法提供一个更老套的替代方法。如果您可以控制 PHP 下载脚本,也许您可​​以在下载完成后简单地输出 javascript。或者可能重定向到运行 javascript 的 html 页面。 javascript 运行,然后可以尝试调用父框架中的某些内容。什么会起作用取决于您的应用是否在同一个域中运行

               

              同域

              同一域框架可以只使用框架javascript对象来相互引用。所以它可能是这样的,在你的单页应用程序中,你可以有类似的东西

              window.downloadHasFinished=function(str){ //Global pollution. More unique name?
                  //code to be run when download has finished
              }
              

              对于你下载的 php 脚本,你可以让它在完成后输出这个 html+javascript

              <script>
              if(parent && parent.downloadHasFinished)
                  parent.downloadHasFinished("if you want to pass a data. maybe export url?")
              </script>
              

               

              不同的域

              对于不同的域,我们可以使用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
                  }
              });
              

              在你的 php 下载脚本中你可以让它输出这个 html+javascript

              <script>
              parent.postMessage("if you want to pass data",
                 "http://downloadphp.anotherdomain.com");
              </script>
              
              1. Parent Demo
              2. Child jsfiddle

               

              结论

              老实说,如果其他答案有效,您可能应该使用这些答案。我只是觉得这是一个有趣的选择,所以我把它贴出来了。

              【讨论】:

                【解决方案12】:

                试试这个(模式)

                    $(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")
                    });
                

                jsfiddle http://jsfiddle.net/guest271314/frc82/

                【讨论】:

                  【解决方案13】:

                  我能想到两种解决方案。要么你有 PHP 将它的进度发布到一个 MySQL 表,从前端将使用 AJAX 调用提取信息以检查生成的进度。使用在访问页面时生成的某种唯一键对于同时生成 excel 文件的多人来说是理想的。

                  另一种解决方案是使用 nodejs,然后在 PHP 中使用 cURL 或套接字将 excel 文件的进度发布到 nodejs 服务。然后,当在 nodejs 中接收来自 PHP 的更新时,您只需为正确的套接字编写 excel 文件的进度。不过,这将切断一些浏览器支持。除非你使用外部库来为几乎所有浏览器和版本带来 websocket 支持。

                  希望这个答案有所帮助。我去年也有同样的问题。结束了 AJAX 轮询,让 PHP 即时发布进度。

                  【讨论】:

                    猜你喜欢
                    • 2013-06-14
                    • 2012-05-22
                    • 2013-06-23
                    • 2012-10-24
                    • 1970-01-01
                    • 2013-07-27
                    • 2010-10-19
                    • 2012-08-29
                    相关资源
                    最近更新 更多