在微博上看到有人提及不使用定时器实现iframe自适应(onReadyStateChange + onLoad + onResize + onDOMSubtreeModified),然后就去折腾了,这篇与之前的文章:《不使用定时器实现onhashchange》有点类似
/*****此方法暂时只支持同域下,跨域的问题有待解决****/
以往要使iframe的高度自适应,往往用定时器在跑,这个方法不错。但如果遇到这样的场景,可能会有点问题,就是某个页面嵌入一个app页面(iframe),
而这个app页面,可能经常会发生一些dom的更改,而且是由成千上万的第三方开发者开发的。而且如果定时器一直开着(只要iframe存在),总归不太好~
这样就面临着一个问题:
开发者可能需要对DOM进行修改,而iframe的高度如果需要改变,就必须由第三方开发者调用父层的,每一处DOM修改都要调用一次…
把调整iframe高度的方法暴露给第三方开发者,显示不大合适。有没有更好的方法,有,那就是DOMSubtreeModified。
在折腾的过程中,其实遇到了很我问题,不过基本上通过google就可以解决掉。某人讲的话还是挺有道理的:“Web前端开发无难点,贵在研究问题的精神和过程,方法论只是结果,价值观才是精髓~”
这个属于DOM 3 Level的事件,关于此事件的详情,可以参考以下网址:
相应的还有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; 600px;}
14: button {position:absolute; left:20px; top:20px; 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后才有效果,详见代码)
Chrome:
FF:
Safari:
Opera:
完整例子的下载地址>>