不使用定时器实现iframe的自适应高度

在微博上看到有人提及不使用定时器实现iframe自适应(onReadyStateChange + onLoad + onResize + onDOMSubtreeModified),然后就去折腾了,这篇与之前的文章:《不使用定时器实现onhashchange》有点类似

 

/*****此方法暂时只支持同域下,跨域的问题有待解决****/

 

以往要使iframe的高度自适应,往往用定时器在跑,这个方法不错。但如果遇到这样的场景,可能会有点问题,就是某个页面嵌入一个app页面(iframe),

而这个app页面,可能经常会发生一些dom的更改,而且是由成千上万的第三方开发者开发的。而且如果定时器一直开着(只要iframe存在),总归不太好~

 

这样就面临着一个问题:

开发者可能需要对DOM进行修改,而iframe的高度如果需要改变,就必须由第三方开发者调用父层的,每一处DOM修改都要调用一次…

把调整iframe高度的方法暴露给第三方开发者,显示不大合适。有没有更好的方法,有,那就是DOMSubtreeModified。

 

在折腾的过程中,其实遇到了很我问题,不过基本上通过google就可以解决掉。某人讲的话还是挺有道理的:“Web前端开发无难点,贵在研究问题的精神和过程,方法论只是结果,价值观才是精髓~”

 

这个属于DOM 3 Level的事件,关于此事件的详情,可以参考以下网址:

MDC DOMSubtreeModified >>

W3C DOMSubtreeModified>>

 

相应的还有DOMAttrModified、DOMNodeInserted、DOMNodeRemoved等等事件

举个DOMSubtreeModified的简单例子:

   1: /**
   2:  * ( modified from Nicholas's book )
   3:  */
   4: (function() {
   5:     var _EventUtil = {
   6:         /**
   7:          * 注册event handler
   8:          */
   9:         addHandler: function( element, type, handler ) {
  10:             if( element.addEventListener ) {
  11:                 element.addEventListener( type, handler, false );
  12:             } else if( element.attachEvent ) {
  13:                 element.attachEvent( 'on' + type, handler );
  14:             } else {
  15:                 element[ 'on' + type ] = handler;
  16:             }
  17:         },
  18:         /**
  19:          * 停止event capturing and bubbling
  20:          */
  21:         preventDefault: function( event ) {
  22:             if( event.preventDefault ) {
  23:                 event.preventDefault();
  24:             } else {
  25:                 event.returnValue = false;
  26:             }
  27:         },
  28:         /**
  29:          * 获取事件触发的事件源
  30:          */
  31:         target: function( event ) {
  32:             return event.target || event.srcElement;
  33:         },
  34:         /**
  35:          * 获取event对象
  36:          */
  37:         event: function( event ) {
  38:             return event || window.event;
  39:         }
  40:     }
  41:     window.EventUtil = _EventUtil;
  42: })();

 

页面内容

   1: <a href="#" id="show">Show</a>
   2: <div style="display: none;" id="sqr1">
   3: </div>

绑定的事件:

   1: var attrChangeListener = function( elem, fn ) {
   2:     EventUtil.addHandler( elem, 'DOMSubtreeModified', fn );
   3: };
   4:  
   5: EventUtil.addHandler( window, 'load', function() {
   6:     var showElem = document.getElementById( 'show' );
   7:     var sqr1 = document.getElementById( 'sqr1' );
   8:     
   9:     EventUtil.addHandler( showElem, 'click', function() {
  10:         if( sqr1.style.setAttribute ) {
  11:             sqr1.style.setAttribute( 'display', 'block' );
  12:         } else {
  13:             sqr1.setAttribute( 'style', 'display:block;' );
  14:         }
  15:     } )
  16:     
  17:     attrChangeListener( sqr1, function( event ) {
  18:         alert( EventUtil.target( event ) );
  19:         alert( event.type );
  20:         alert( 'Attrubute Name:' + ( event.attrName || '' ) );
  21:         alert( 'Attribute Change:' + ( event.attrChange || '' ) );
  22:         alert( 'Previous value:' + ( event.prevValue || '' ) );
  23:         alert( 'New value:' + ( event.newValue || '' ) );
  24:     } );
  25: } );
  26:         

在线预览地址>>  请使用Firefox进行查看

 

解决iframe自适应高度的问题,比较理想的办法是:

iframe的onload前使用定时器修改iframe的高度,在onload后清除定时器,然后监听iframe它的document的DOMSubtreeModified事件。为什么在onload之前还要使用定时器呢?防止iframe页面加载资源过久,页面的高度显示上会有问题。而监听DOMSubtreeModified事件的主要作用是为了省去在iframe内修改dom时,每一次都要主动调用一次修改iframe高度的方法。这样就让iframe开发者,只需要专注自身页面的逻辑结构,不用再考虑每修改dom之处都要调用修改iframe高度的方法。

 

注明:文章的标题是不使用定时器,而上面我提到定时器,主要是担心iframe的domready与onload的那段时间内,iframe的高度看上去会很怪异(实际开发中这一段时间有多长,影响有多大,到底要不要加定时器,还是需要根据实际情况再衡量一下)

 

下面的实现,我没有考虑使用定时器(如果加上了就不符合文章的标题了,而在实际开发中可能还是需要,视情况而定了),关于使用定时器使iframe自适应高度,可以参考口碑的那篇文章:再谈iframe自适应高度>>

 

还有一点要提一下:chrome的某些版本中,子页(iframe)调用parent时会被禁止,而导致页面没有效果,放在web上跑就好了。这个问题可以参考这里找到说明,地址>>

 

下面直接上代码了,代码中有参考老外的例子,在整理过程中没有及时的保存这些链接,有空再补上。

 

例子是index.html嵌入iframe.html页面,index.html代码:

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3:  <head>
   4:   <title>iframe 高度自适应的例子</title>
   5:   <meta name="generator" content="editplus" />
   6:   <meta name="author" content="" />
   7:   <meta name="keywords" content="" />
   8:   <meta name="description" content="" />
   9: <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  10:   <style type="text/css">
  11:     *  {margin:0; padding:0;}
  12:     body {background-color:#fff; padding:20px;}
  13:     iframe {border:1px solid #406c99; width:600px;}
  14:     button {position:absolute; left:20px; top:20px; width:80px;}
  15:   </style>
  16:  </head>
  17:  
  18:  <body>
  19:   <button onclick="createFrame()">创建iframe</button>
  20:  </body>
  21: </html>
  22: <script type="text/javascript">
  23: function createFrame() {
  24:     document.getElementsByTagName("button")[0].disabled = true;
  25:  
  26:     frameHandler.create();
  27: }
  28:  
  29: var frameHandler = function() {
  30:     var inner;
  31:     
  32:     var _iframeName = "iframe_iden_1";
  33:  
  34:     return inner = {
  35:         _isSupport : false,
  36:         init : function() {
  37:             
  38:         },
  39:         create : function() {
  40:             var frame = null;
  41:  
  42:             if (window.ActiveXObject) {
  43:                 frame = document.createElement('<iframe name="'+_iframeName+'">');
  44:             } else {
  45:                 frame = document.createElement("iframe");
  46:                 frame.setAttribute("name", _iframeName);
  47:             }
  48:             
  49:             frame.setAttribute("id", _iframeName);
  50:             frame.frameBorder = "none";
  51:             frame.scrolling = "no";
  52:             frame.style.marginTop = '40px';
  53:             
  54:             document.body.appendChild(frame);
  55:  
  56:             frame.contentWindow.focus();
  57:  
  58:             inner.check();
  59:             
  60:             if (inner._isSupport) {
  61:                 if (!frame.addEventListener) {
  62:                     frame.attachEvent("onload", function() {
  63:                         frame.detachEvent("onload", arguments.callee);
  64:                         inner.adjustFrameHeight();
  65:                         frame.contentWindow.attachEvent("onresize", inner.adjustFrameHeight);                        
  66:                     });
  67:                     
  68:                 } else {
  69:                     frame.addEventListener("load", function() {
  70:                         frame.removeEventListener('load', arguments.callee, false);
  71:                         inner.adjustFrameHeight();
  72:                         frame.contentWindow.document.documentElement.addEventListener('DOMSubtreeModified', inner.adjustFrameHeight, false);
  73:                     }, false);
  74:                 }
  75:             } else if (frame.addEventListener) {// for FF 2, Safari 2, Opera 9.6+
  76:                     frame.addEventListener("load", function() {
  77:                         var fn = arguments.callee;
  78:                         setTimeout(function() {
  79:                             frame.removeEventListener('load', fn, false);
  80:                         }, 100);
  81:                         
  82:                         inner.adjustFrameHeight();    
  83:                         frame.contentWindow.document.documentElement.addEventListener('DOMNodeInserted', inner.adjustFrameHeight, false);
  84:                         frame.contentWindow.document.documentElement.addEventListener('DOMNodeRemoved', inner.adjustFrameHeight, false);
  85:                     }, false);
  86:             }
  87:  
  88:             frame.src = "iframe.html";
  89:         },
  90:         getFrame : function() {
  91:             return document.getElementById(_iframeName).contentWindow;
  92:         },
  93:         adjustFrameHeight : function() {
  94:             var elem = document.getElementById(_iframeName);
  95:  
  96:             elem.style.height = Math.max(elem.contentWindow.document.body.scrollHeight, elem.contentWindow.document.documentElement.scrollHeight) + 'px';
  97:         },
  98:         check : function() {
  99:             var remain = 1, 
 100:                 doc = document.documentElement, 
 101:                 dummy;
 102:             
 103:             if (doc.addEventListener) {
 104:                 doc.addEventListener("DOMSubtreeModified", function() {
 105:                     inner._isSupport = true;
 106:                     doc.removeEventListener("DOMSubtreeModified", arguments.callee, false);
 107:                 }, false);
 108:             } else {
 109:                 inner._isSupport = true;
 110:                 return ;
 111:             }
 112:  
 113:             dummy = document.createElement("div");
 114:             doc.appendChild( dummy );
 115:             doc.removeChild( dummy );
 116:         }
 117:     }
 118: }();

iframe.html的代码:

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3:  <head>
   4:   <title> new document </title>
   5:   <meta name="generator" content="editplus" />
   6:   <meta name="author" content="" />
   7:   <meta name="keywords" content="" />
   8:   <meta name="description" content="" />
   9:   <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  10:   <style type="text/css">
  11:     * {margin:0; padding:0;}
  12:     body { padding:5px; background-color:#fff;}
  13:     button {margin:0 10px;}
  14:   </style>
  15:  </head>
  16:  
  17:  <body>
  18:   xxxxxxxxx
  19:  
  20: <button disabled="true" onclick="start()">Start Interval</button>
  21: <button onclick="stop()">Stop Interval</button>
  22: <button onclick="clearAllChilds()">Clear All</button>
  23:  
  24:  </body>
  25: </html>
  26: <script type="text/javascript">
  27: var g_interval_timer = null;
  28:  
  29: function start() {
  30:     commonHandler(0);
  31:  
  32:     g_interval_timer = setInterval(function(){    
  33:         var div = document.createElement("div");
  34:             div.innerHTML = "<p>dddddddddddddddddddddddddddd</p>";
  35:         document.body.appendChild(div);
  36:     }, 100);    
  37: }
  38:  
  39: function stop() {
  40:     commonHandler(1);    
  41: }
  42:  
  43: function clearAllChilds() {
  44:     stop();
  45:     var childs = document.getElementsByTagName("div");
  46:     
  47:     while (childs.length) {
  48:         document.body.removeChild(childs[0]);
  49:     }
  50: }
  51:  
  52: function commonHandler() {
  53:     var buttons = document.getElementsByTagName("button"),
  54:         idx = arguments[0];
  55:  
  56:     if (g_interval_timer) {
  57:         clearInterval(g_interval_timer);
  58:         g_interval_timer = null;
  59:     }
  60:     
  61:     buttons[idx].disabled = true;
  62:     buttons[1 - idx].disabled = false;
  63: }
  64:  
  65: start();

 

 

下面本地用nginx建了一个域名ajax.com运行的效果:

IE6(7、8效果与此类似,但不知为何一定要让iframe foucs后才有效果,详见代码)

image

Chrome:

image

 

FF:

image

Safari:

image

 

Opera:

image

 

完整例子的下载地址>>

posted @ 2011-03-09 01:48  meteoric_cry  阅读(1729)  评论(0编辑  收藏  举报