iframe之間通訊問題及iframe自適應高度問題

龍恩0707發表於2013-09-29

下面本人來談談iframe之間通訊問題及iframe自適應高度問題。

 1. iframe通訊 分為:同域通訊 和 跨域通訊。所謂同域通訊是指 http://localhost/demo/iframe/iframeA.html 下的a.html頁面巢狀 iframe 比如: <iframe src="http://localhost/demo/iframe/iframeB.html" id="iframeA" name="iframeA">的B.html頁面,這兩個頁面資料進行通訊,比如我想在父頁面A.html 呼叫子頁面當中的函式 我們很容易想到或者google下 document.getElementById('iframeA').contentWindow.b(); 這種方法,其中b 是子頁面B.html中的一個函式。但是這樣呼叫下有個問題我糾結了很久,就是既然在火狐下報這樣的錯誤, 如下:
b不是個函式 但是我在子頁面明明定義了這麼一個函式,那麼為什麼會報這樣的錯誤呢?經過仔細分析及google,發現有這麼一個問題需要理解,當iframe沒有載入完成後 我就去執行這個js會報這樣的錯誤,所以就試著在火狐下 用iframe.onload 這個函式 進行測試,果然沒有報錯,是正確的 所以就確定是這個問題。所以就想寫個相容IE和火狐 google寫個函式 來確定iframe已經載入完成!,其實給個回撥函式來呼叫我們上面的方法。

綜合上面的思路 就可以寫個這樣的程式碼:

<iframe src="http://localhost/demo/iframe/iframeB.html" id="iframeA" name="iframeA"></iframe>
    <div id="topName">topNddddddddddddddddame</div>
    <script>
         function A(){
            alert("A");
        }
        var iframe = document.getElementById('iframeA');
        
        iframeIsLoad(iframe,function(){
            var obj = document.getElementById('iframeA').contentWindow;
            obj.b();
        });
        
            
        
        
     function iframeIsLoad(iframe,callback){
        if(iframe.attachEvent) {
            
            iframe.attachEvent('onload',function(){
                callback && callback();
            });
        }else {
            iframe.onload = function(){
                callback && callback();
            }
        }
     }

    </script>

B.html 程式碼如下:

var b = function(){
            alert("B");
        }

2.子頁面呼叫父頁面的函式很簡單,只要這樣搞下就ok了,window.parent.A();

3. 子頁面取父頁面元素的值: window.parent.document.getElementById("topName").innerHTML等方法。

二: iframe跨域通訊。

 iframe跨域訪問一般分為2種情況,第一種是同主域,不同子域的跨域。 第二種是:不同主域跨域。

 一、 是同主域下面,不同子域之間的跨域;可以通過document.domain 來設定相同的主域來解決。

       假如現在我有個域 abc.example.com 下有個頁面叫abc.html, 頁面上巢狀了一個iframe 如下:<iframe src="http://def.example.com/demo/def.html"  id="iframe2" style="display:none;"></iframe>,我想在abc域下的頁面abc.html 訪問 def域下的def.html  我們都知道由於安全性 遊覽器的同源策略的限制,js不能操作頁面不同域下 不同協議下 不同埠的頁面,所以就要解決跨域訪問了,假如父頁面abc.html 頁面有個js函式:function test(){console.log(1);}; 我想在子頁面呼叫這個函式 還是按照上面的同域方式呼叫 parent.test();這樣,通過在火狐下看 已經跨域了 解決的辦法是 在各個js函式頂部 加一句 document.domain = 'example.com',就可以解決了。abc.html程式碼如下:

<iframe src="http://def.example.com/demo/def.html"  id="iframe2" style="display:none;"></iframe>

// 跨域 子頁呼叫父頁的 函式 (假設是下面test函式)
document.domain = 'example.com';
function test(){console.log(1);};

def.html程式碼如下:

/*
 * 子頁呼叫父頁的方法
 */
document.domain = 'example.com';
//window.top.test();
window.parent.test();

還是這兩個頁面 我想父頁呼叫子頁 如下方法:
a.html程式碼如下:

/*
 * 跨域 父頁想呼叫子頁的的函式
 */
document.domain = 'example.com';
var iframe = document.getElementById('iframe2');
iframeIsLoad(iframe,function(){
    var obj = iframe.contentWindow;
         obj.child();
});
function iframeIsLoad(iframe,callback){
        if(iframe.attachEvent) {
            iframe.attachEvent('onload',function(){
                callback && callback();
            });
        }else {
            iframe.onload = function(){
                callback && callback();
            }
        }
 }

假如現在def.html頁面有個child函式 程式碼如下:

document.domain = 'example.com';
function child(){console.log('我是子頁');}

就可以跨域呼叫了 不管是子頁面呼叫父頁面 還是父頁面呼叫子頁面。一切ok!

二、 是不同主域跨域;

 雖然google有幾種方法關於不同主域上的跨域問題 有通過location.hash方法或者window.name方法或者html5及flash等等,但是我覺得下面iframe這種方法值得學習下,

如下圖所示:域a.com的頁面request.html(即http://a.com/demo/ajax/ajaxproxy/request.html)裡面巢狀了一個iframe指向域b.com(http://b.com/demo/ajax/ajaxproxy/response.html)的response.html,而response.html裡又巢狀了域a.com的proxy.html。

 

思路:要實現a.com域下的request.html頁面請求域b.com下的process.php,可以將請求引數通過url傳給response.html,由response.html向process.php發起真正的ajax請求(response.html與process.php都屬於域b.com),然後將返回的結果通過url傳給proxy.html,最後由於proxy.html和request.html是在同個域下,所以可以在proxy.html利用window.top 將結果返回在request.html完成真正的跨域。

 

ok, 先看看頁面結構

a.com域下有:

     request.html

     proxy.html

b.com域下有:

    response.html

    process.php

先來看看request.html頁面如下:

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
 </head>
	
 <body>
	<p id="result">這裡將會填上響應的結果</p>
    <a id="sendBtn" href="javascript:void(0)">點選,傳送跨域請求</a>
    <iframe id="serverIf" style="display:none"></iframe>

	<script>
		document.getElementById('sendBtn').onclick = function() {
			var url = 'http://b.com/demo/ajax/ajaxproxy/reponse.html',
				fn = 'GetPerson',          //這是定義在response.html的方法
				reqdata = '{"id" : 24}',   //這是請求的引數
				callback = "CallBack";     //這是請求全過程完成後執行的回撥函式,執行最後的動作

			CrossRequest(url, fn, reqdata, callback);  //傳送請求
		}

		function CrossRequest(url,fn,reqdata,callback) {
			var server = document.getElementById('serverIf');
			server.src = url + '?fn=' +encodeURIComponent(fn) + "&data=" +encodeURIComponent(reqdata) + "&callback="+encodeURIComponent(callback);
		}
		//回撥函式
		function CallBack(data) {
			var str = "My name is " + data.name + ". I am a " + data.sex + ". I am " + data.age + " years old.";
			 document.getElementById("result").innerHTML = str;
		}
	</script>
 </body>
</html>
 

  這個頁面其實就是要告訴response.html:我要讓你執行你定義好的方法GetPerson,並且要用我給你的引數'{"id" : 24}'。response.html純粹是負責將CallBack這個方法名傳遞給下一位仁兄proxy.html,而proxy.html拿到了CallBack這個方法名就可以執行了,因為proxy.html和request.html是同域的。

response.html程式碼如下:

      

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
 </head>

 <body>
	 <iframe id="proxy"></iframe>
	<script>
		// 通用方法 ajax請求
		function _request (reqdata,url,callback) {
			var xmlhttp;
			if(window.XMLHttpRequest) {
				xmlhttp = new XMLHttpRequest();
			}else {
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
			}

			xmlhttp.onreadystatechange = function(){
				if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
					var data = xmlhttp.responseText;
					callback(data);
				}
			}
			xmlhttp.open('POST',url);
			xmlhttp.setRequestHeader("Content-Type", "application/json; charset=utf-8");
			xmlhttp.send(reqdata);
		}

		// 通用方法 獲取url引數
		function _getQuery(key) {
			var query = location.href.split('?')[1],
				value = decodeURIComponent(query.split(key + "=")[1].split("&")[0]);
			return value;
		}

		//向process.php傳送ajax請求
		function GetPerson(reqdata,callback) {
			var url = 'http://b.com/demo/ajax/ajaxproxy/process.php';
			var fn = function(data) {
				var proxy = document.getElementById('proxy');
				proxy.src = "http://a.com/demo/ajax/ajaxproxy/Proxy.html?data=" + encodeURIComponent(data) + "&callback=" + encodeURIComponent(callback);
			};
			_request(reqdata, url, fn);
		}

		(function(){
			var fn = _getQuery('fn'),
				reqdata = _getQuery("data"),
				callback = _getQuery("callback");
           eval(fn + "('" + reqdata +"', '" + callback + "')");
		})();
	</script>
 </body>
</html>

  

      這裡其實就是接收來自request.html的請求得到請求引數和方法後向伺服器process.php發出真正的ajax請求,然後將從伺服器返回的資料以及從request.html傳過來的回撥函式名傳遞給proxy.html。 

接下來看看php程式碼如下,其實就是想返回一個json資料:

<?php 
	$data = json_decode(file_get_contents("php://input"));
	header("Content-Type: application/json; charset=utf-8");
	echo ('{"id" : ' . $data->id . ', "age" : 24, "sex" : "boy", "name" : "huangxueming"}');
?>

最後就是proxy.html程式碼:

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
 </head>

 <body>
	<script>
		 function _getUrl(key) {//通用方法,獲取URL引數
                       var query = location.href.split("?")[1],
			    value = decodeURIComponent(query.split(key + "=")[1].split("&")[0]);
                    return value;
               }
         (function() {
             var callback = _getUrl("callback"),
				 data = _getUrl("data");
             eval("window.top." + decodeURIComponent(callback) + "(" + decodeURIComponent(data) + ")");
         })();
	</script>
 </body>
</html>

  這裡也是最後一步了,proxy終於拿到了request.html透過response.html傳過來的回撥函式名以及從response.html直接傳過來的響應資料,利用window.top執行request.html裡定義的回撥函式。

 三:iframe高度自適應的問題。

    iframe高度自適應分為2種,一種是同域下自適應  另外一種是跨域下自適應,下面我們來看看同域下iframe高度自適應的問題。

   1.同域下iframe高度自適應的問題:

        思路:獲取被巢狀iframe元素,通過JavaScript取得被巢狀頁面最終高度,然後在主頁面進行設定來實現。

假如我們demo有iframe1.html和iframe2.html 下面貼上iframe1.html程式碼如下:

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
  <style>
	*{margin:0;padding:0;}
  </style>
 </head>

 <body>
	<iframe src="http://a.com/demo/ajax/iframeheight/iframe2.html" style="width:100%;border:1px solid #333;" frameborder="0" id="iframe"></iframe>

	<script>
		window.onload = function() {
			var iframeid = document.getElementById('iframe');
			if(iframeid && !window.opera) {
				if(iframeid.contentDocument && iframeid.contentDocument.body.offsetHeight) {
					iframeid.height = iframeid.contentDocument.body.offsetHeight;
				}else if(iframeid.Document && iframeid.Document.body.scrollHeight){ 
					iframeid.height = iframeid.Document.body.scrollHeight;
				}
			}
		}
	</script>
 </body>
</html>

iframe2.html

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
  <style>
	*{margin:0;padding:0;}
  </style>
 </head>

 <body>
	<div style="height:500px;"></div>
 </body>
</html>

就可以動態設定iframe1頁面的高度為iframe2的高度了。

 2. 跨域下iframe高度自適應的問題。

    首先我們知道iframe跨域我們是不能用上面js方式來控制了,所以我們只能用箇中間鍵 我們可以在a.com域下iframe1.html頁面巢狀一個b.com域下的iframe2.html頁面,然後我在iframe2.html頁面巢狀個和iframe1.html相同域的iframe3.html頁面了,這樣的話 iframe1.html和iframe3.html就可以無障礙的進行通訊了,因為頁面iframe2.html巢狀iframe3.html,所以iframe2.html可以改寫iframe3.html的href值。

   iframe1中的內容:

      iframe1.html內容主要接受iframe3.html頁面傳過來的內容並且去完成相應的操作。iframe1.html程式碼如下:

<iframe src="http://b.com/demo/ajax/iframeheight/iframe2.html" style="width:400px;height:200px;" id="iframe"></iframe> 

<script>
   var ifr_el = document.getElementById("iframe");
   function getIfrData(data){
    ifr_el.style.height = data+"px";
   }
</script>

 iframe2.html中的內容:

       iframe2.html內容是怎麼把值傳給iframe3.html頁面,剛才說了是將值傳遞到iframe3.html頁面的href中,所以只要修改iframe的src就可以,因為不用重新整理C頁面,所以可以用過hash的方式傳遞給iframe3.html頁面.iframe2.html程式碼如下:

<!DOCTYPE HTML>
<html>
 <head>
  <title> New Document </title>
  <style>
	*{margin:0;padding:0;}
  </style>
 </head>

 <body>
	<iframe id="iframe" src="http://a.com/demo/ajax/iframeheight/iframe3.html" width="0" height="230px"></iframe>

	<script>
		var oldHeight = 0,
		      ifr_el = document.getElementById("iframe");
		
		t && clearInterval(t);
		var t = setInterval(function(){
			var height = document.body.scrollHeight;
			if(oldHeight != height) {
				oldHeight = height;
				ifr_el.src += '#' +oldHeight; 
			}
		},200);
	</script>
 </body>
</html>

         可以看到 預設情況下 iframe1.html 頁面我給iframe2.html的高度是200畫素 但是在iframe2.html我給iframe3.html高度是230畫素,那麼正常情況下是有滾動條的,那麼現在我是想在iframe2.html獲取滾動條的高度,把高度傳給通過iframe3.html的src裡面去,然後在iframe3.html頁面裡獲取這個高度值 傳給iframe1.html(因為iframe1.html和iframe3.html是同域的),所以iframe1.html能取到這個高度值,再設定下本身的高度就是這個值就ok了。

       iframe3.html頁面的唯一功能就是接收iframe2.html頁面通過href傳進來的值並且傳遞給iframe1.html頁面,可到iframe2.html頁面傳來的值可以通過一個定時器不停去檢視location.href是 否被改變,但是這樣感覺效率很低,還有個方式就是在新的瀏覽器中通過onhashchange事件 (IE8+,Chrome5.0+,Firefox3.6+,Safari5.0+,Opera10.6+)來監聽href的改變。

iframe3.html程式碼如下:

<script>
	var oldHeight = 0;

	t && clearInterval(t);
	var t = setInterval(function(){
		var height = location.href.split('#')[1];
		if(height && height != oldHeight) {
			oldHeight = height;
			if(window.parent.parent.getIfrData) {
				window.parent.parent.getIfrData(oldHeight);
			}
		}
	},200);
	</script>

這樣就可以解決通過跨域實現iframe自適應高度的問題了。

相關文章