引用b站视频有用到iframe,看到网易云音乐页面也用的是iframe,顺手研究下(然而几乎用不到)
参考资料:由于网上资料繁杂,将链接在文中给出
iframe页面引用地址问题
本地测试
由于浏览器存在同源限制,被限制的情况下很多功能都无法使用,难以测试,故将此放在开头。之后可以跳着看。
说下我最先遇到的坑。探究过程中我使用的是chrome 62.0.3202.94
版本,chrome
对同源限制比IE
严格,我首先是直接使用本地文件,即在chrome
中直接打开含有iframe
的html
文件,iframe
的地址指为src="./child.html"
。结果chrome
报告跨域,禁止调用子页面的函数。而在IE11
中允许运行ActiveX
后便可正常调用子页面中的函数。若要继续在chrome
上测试,则需开启本地服务器,并设置iframe
地址为相应的路由。
跨子域/端口
以下内容引用自这里,有修改。
document.domain
是比较常用的跨域方法。实现最简单,但只能用于同一个主域下不同子域之间或不同端口之间的跨域请求,比如:foo.com
和img.foo.com
之间,img1.foo.com
和img2.foo.com
之间。只要把两个页面的document.domain
都指向主域就可以了,比如:document.domain='foo.com'
。设置好后父页面和子页面就可以像同一个域下两个页面之间访问了。
注:由于条件有限,只测试了同一主域下不同端口(亦为增加内容),在chrome
测得。
1 | <!-- foo.com/a.html --> |
其他跨域访问(主要针对主域名不同)
由于浏览器同源限制,父子页面都无法互相访问(无法获取document
和window
下大部分对象)。但父页面依然可以获取iframe
的部分信息(但是没有有用的信息)。子界面也可以访问parent
,但无法获取parent
的location
。
跨域时可以获取parent
是有适用情况的,只是较少见。
以下内容来自这里,有修改。
前提,
www.a.com
下a1.html
,a1.html
内iframe
调用了www.b.com
下的b.html
,b.html
下iframe
调用了www.a.com
下的a2.html
。即如下结构
1 | <!-- a1.html --> |
b.html
无法直接访问a1.html
的对象,因为涉及到跨域,但依旧可以用parent
访问a1.html
,同样a2.html
的parent
可以访问b.html
。a1.html
和a2.html
同域,a2.html
是可以访问a1.html
下的对象的。故a2.html
可用parent.parent.js对象
访问a1.html
中的对象。
调用
注意:以下均在同源环境下测得
父页面调用子页面
栗子:<iframe id="child" name="ifr" src="/child"></iframe>
要调用iframe
中的函数,则要获取iframe
的window
对象,可以使用ifr
或window.frames[0]
或window.frames['ifr']
或document.getElementById('child').contentWindow
。(其中window.frames[0]
和window.frames['ifr']
的window
可缺省)
要操作iframe
中的DOM
节点,即获取iframe
的document
,则需要使用ifr.document
或frames[0].document
或frames['ifr'].document
或document.getElementById('child').contentDocument
或document.getElementById('child').contentWindow.document
子页面调用父页面
要调用父页面中的函数,则要获取父页面的window
对象,可通过window.parent
获取。同时window.top
可以获取顶层页面的window
对象。(window.parent
和window.top
的window
可以缺省)
要操作父页面中的DOM
节点,即获取父页面的document
,可以使用parent.document
。
防止被其他网页调用
来自这里
1 | if(window!==window.top){ |
子页面刷新
若是父页面控制,则调用frames['ifr'].location.reload()
(只能在同源下使用)即可。若是子页面控制,则调用window.location.reload()
即可。
iframe的onload事件
由于iframe
加载较慢,也因此,onload
只能设置在<iframe>
标签中,不然无法通过js
获取到iframe
节点(即使script
标签放在父页面body
尾部),也就无从设置。若onload
事件直接设置在父页面的<iframe>
标签中,会获取不到子页面的document
。若要解决可以将onload
设置在子页面的body
上,此时可以获取子页面中的大部分内容。(对于iframe
的加载过程没有详细了解,有兴趣可自行查找资料)
子页面转跳
子页面进行转跳后,可以使用浏览器的回退按钮回退。但不做任何设置,浏览器地址栏不会发生改变。
使地址栏发生改变但只更新iframe
中的内容,可以利用hash
,即通过top.location.hash
来设置。
以下内容为探究过程,若无耐心看完可直接跳过
注:以下代码在本地服务端中测试通过,服务端仅设置相关路由并返回文件。
然后这里就有一个坑了:如果设置点击iframe
中的链接后更改hash
,表面上符合要求,但是浏览器的历史回退却出现了两个地址,也就是说,要回到我们点击的时候的页面,需要点击两次后退按钮,非常不友好。而这是因为浏览器会记录iframe
中的转跳,产生了一个历史记录,然后我们更改hash
,浏览器也会产生一个历史记录。
解决办法就是使用location.replace()
来对iframe
中的页面进行更新,因为使用location.replace()
不会产生历史记录。详见MDN
(注:不通过更改iframe
的src
属性来刷新页面,因为这种方法也会产生历史记录)
但这只是其中一步。由于我们是通过点击<a>
标签来切换页面的,但是我们要用location.replace()
来进行更新,代替<a>
标签默认事件进行页面切换,所以还需要阻止<a>
标签默认事件的传播,即使用event.preventDefault()。
暂时总结一下过程:在iframe
中点击链接–><a>
标签默认事件被禁止–>使用top.location.hash
更改地址栏地址–>使用location.replace()
更新iframe
内容。似乎可以了,地址栏的确是只有一个历史记录。但是点一下后退,只有地址栏发生了改变,iframe
的内容却没变。这是理所当然的,我们没有更新iframe
的内容啊!
点击后退后,页面的hash
发生了变化,我们可以监控这个变化,来更新iframe
。好在html5
有一个onhashchange事件,为我们指明了道路。只需要在父(顶层)页面添加onhashchang
事件,设置好相关函数更新iframe
即可。
然后我们的流程变成了这样:在iframe
中点击链接–><a>
标签默认事件被禁止–>使用top.location.hash
更改地址栏地址–>父页面onhashchange
监听到改变–>使用location.replace()
更新iframe
内容。
好像很完美,恩,那点一下刷新试试?你会发现刷新过后iframe
不会显示当前的页面,而是显示了原始src
属性对应的页面(如果设置了的话,不设置会是空白),就算是使用js
更改过src
,刷新后也是显示原先的src
。极其不友好。
为了使刷新也符合我们的要求,可以在主页面<body>
尾部添加一个hash
判断,既做初始化操作(可以不需要设置iframe
的src
),又保证iframe
内容不会变动。代码如下。
1 | if(location.hash===''){ |
至此,历史功能和刷新功能已经全部符合预期。
最后还有页面重定向。由于我们是用的hash
记录位置,如果不想让iframe
中的子页面被单独打开,只能在前端设置转跳。代码如下:1
2
3
4if(window===window.top){
window.location.href = window.location.origin+
'/#'+window.location.pathname
}
总结:onhashchange
监听hash
变化,在前进后退时更新iframe
。location.hash
更改地址栏地址。location.replace()
更新iframe
内容,不会产生历史记录。event.preventDefault()
禁用<a>
默认事件,防止产生历史记录
完整代码如下:
1 | <!-- parent.html --> |
1 | <!-- child.html --> |
1 | <!-- about.html --> |
感想
此方法实际上是SPA
的实现方式的一种(无卵用(),其用hash
记录地址的方式可以用于其它方式。若此方法要投入实际运作,需要额外的代码避免意外。(如排除无效链接、onhashchange兼容等)
注:历史记录的问题还可以使用history.pushState来制定相应方案,此处不作探讨。
后记
此篇主要为看到网易云网页版源码之后,一时兴起的瞎折腾。对于iframe
的其他属性资料很多,不做整理。
文章完成后,查找关键词iframe单页面应用
时找到一个iframe
单页面组件,有兴趣可以查看这里。同时找到一篇思路与本篇一样的文章,但其中并无代码,顺手放出来,查看这里。
看到一篇关于onhashchange
对IE6-11
兼容性的探讨,有需求可看这里