模态对话框导致setTimeout失效的解决方案(一)
web开发中,大家有可能经历过下面这种问题:
① 画面上通过setTimeout启动了一个定时器,用以动态更新画面上的某个组件(比如,1 秒刷新一次时间中的秒数)
② 当页面弹出一个模态对话框(通过 showModalDialog打开)时候,你会发现画面上的setTimeout失效了(比如,时间不再是一秒刷新一次了)
发生这个问题的原因大家可能都知道:模态对话框的特性导致了这个问题。在关闭模态对话框之前,用户无法将父窗口进行任何操作。而且Javascript又是单线程的,导致在模态对话框打开之后,被启动的setTimeout定时器,即时到了指定时间之后,也没有机会被执行。
那么,如果解决这个问题呢?
目前,我共找到两个解决方案,本篇blog中先描述方案一。下面开始。
方案一是采用 iframe 来实现的。
该方案的大体思路如下:
① 新建一个主HTML文件,如上面的index.html,该文件仅仅用于存放两个iframe,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>setTimeout无效的解决方案(一)</title> </head> <body> <!-- 这个iframe用来显示画面内容 --> <div id="frameDiv1"> <iframe id="dlgFrame1" name="dlgFrame1" src="frame1.html" style="width: 100%; height: 100%; border: none;"></iframe> </div> <!-- 这个iframe仅仅用于打开模态对话框,所以将其隐藏 --> <div id="frameDiv2" style="display: none;"> <iframe id="dlgFrame2" name="dlgFrame2" src="frame2.html"></iframe> </div> </body> </html>
② 新建一个iframe,如上面的iframe1.html,此文件用来展示你想在画面上显示的东西,代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> function start() { (event.target || event.srcElement)["disabled"] = true; setTimeout(function(){ document.querySelector("#txt").innerText = Math.random(); setTimeout(arguments.callee, 1000); }, 1000); } function dlgOpenButton() { // 获取iframe2的window对象,通过此window对象打开模态对话框 document.querySelector("#txt2").innerText = "模态画面打开中。。。"; var win = window.top.document.querySelector("#dlgFrame2").contentWindow; var val = win.showModalDialog("dialog.html",Math.random()); document.querySelector("#txt2").innerText = "模态画面关闭了,返回值为: " + val; // 如果是通过alert来打开的话,仍然是不行的。这个目前还无解。 //var val = win.alert("modal dialog"); //document.querySelector("#txt2").innerText = "模态画面关闭了,返回值为: " + val; } </script> </head> <body> <button type="button" onclick="start()">启动定时器</button> <button type="button" onclick="dlgOpenButton()">打开模态对话框</button> <div> <p style="float: left;">定时更新内容:</p> <p id="txt" style="float: left;"></p> </div> <div style="float: left; clear: left;"> <p style="float: left;">模态对话框状态:</p> <p id="txt2"></p> </div> </body> </html>
③ 新建另一个iframe,如上面的iframe2.html,此文件仅仅用于打开模态对话框,代码如下:
<!DOCTYPE html> <html> <head> </head> <body> </body> </html>
④ 最后,要打开的模态对话框的代码如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>我是模态对话框</title> <script> window.onload = function() { var para = window.dialogArguments; document.querySelector("#fromParent").innerText = para; }; window.onunload = function() { window.returnValue = Math.random(); }; </script> </head> <body> <div> <p>我是模态对话框,父画面传给我的值是: </p> <p id="fromParent"></p> </div> <br/> </body> </html>
上面将相关代码添完了,下面简单说一下此方案的思路:首先,我们是准备在iframe1里启动定时器的,所以,我们为了不让iframe1中的定时器失效,我们就不能再在iframe1中打开模态对话框,理由在上面已经描述过了。这样一来,我们只能寻找另外一种方案,使得能够打开模态对话框,同时又不阻断iframe1中的定时器。这样想的话,我们只好借用一下其他window对象,并调用其showModalDialog方法来打开模态对话框。所以,iframe2就派上用场了。即,在iframe1中,获取到iframe2的window对象,然后打开模态对话框,这种方式打开的模态对话框是不会阻断iframe1中的setTiemout的。
大家可以打开附件iframe.zip,然后运行其中的 index.html,如下图。然后先点击“启动定时器”按钮,然后再点击“打开模态对话框”按钮,观察一下画面,便可验证。
PS:
① 以上方案仅在IE10中进行过了验证。
② 对于alert之类的对话框上述方案无效。