iframe在移动端的限制及其应对策略

iframe使用于移动端其实不少,只是可见的iframe很少。以往我都是用iframe来实现无刷新登录和跨域通信,用来显示内容的地方就是登录那一块。而对于登录来说,登录界面无非,3-4个输入框和1~2个按钮,手机一屏内完全可以显示,所以其实对iframe显示多内容的问题没有过多研究,只知道有以下特性:

1.android 2.3的原生浏览器下iframe不仅不可以被控制高度,它同时不会被它外围的div的overflow:hidden 剪裁!

 

最近由于微信禁止了分享接口的任意调用,需要在规定的域名内才可以调用分享接口。因为公司有公众号,可以正常调用接口,但是测试环境的域名却在允许之外,为保持测试的质量,想通过iframe嵌套的方式实现分享接口调用(其实也可以实现其它接口的调用,同时也可以实现手Q的分享接口调用)。

我的思路如下:

iframe在移动端的限制及其应对策略_第1张图片


测试的结果是,接口是可以完美调用的。不过,却意外发现一个问题。在ios下,iframe内的页面不能滚动(也不存在滚动条)!!!!

如果只是针对轻APP的H5页是可以完美兼容的,但是对于普通带滚动条的页面来说却是个坏消息。需要写个应对策略。首先,要弄清楚,iframe对页面的实际影响有多大,会不会对iframe的所有元素的内部滚动都造成影响了,如果没有没有,就可以考虑把滚动的任务将于节点body完成,而非window完成。

测试的结果是,iframe只对iframe的contentwindow产生不可滚动不带滚动条的影响,而不对contentwindow内的元素造成任何影响。这是个好消息,我可以通过以下代码实现在ios下iframe页面也可以滚动了:

html,body{width: 100%; height: 100%; overflow: hidden;}
body{-webkit-overflow-scrolling:touch; overflow-y:scroll;}

 

有了以上代码基本上就解决了ios下iframe不能滚动的问题了。不过,这里会有一个问题,就是如果iframe页面里使用了window.onscroll事件的话,这个onscroll事件会失效。所以需要写一个统一方法来实现在iframe页面下window.onscroll事件可以被激活,其实,只要判断条件,重写一下window.onscroll事件即可

,以下是重写onscroll的代码段

if(top!=window){
    //处于iframe中
    if(navigator.userAgent.indexOf('AppleWebKit')>=0){
        //ios系统,需要兼容一下window没有滚动条和不能滚动的问题
        var iosStyle=document_createElement_x_x_x_x_x_x_x('style');
        iosStyle.setAttribute('type','text/css');
        iosStyle.innerHTML='\
            html,body{width: 100%; height: 100%; overflow: hidden;}\
            body{-webkit-overflow-scrolling:touch; overflow-y:scroll;}\
        ';
        document.getElementsByTagName_r('head')[0].a(iosStyle);
        //当body.onscroll时,触发window.onscroll
        var scrollEv=document_createEvent('HTMLEvents');
        scrollEv.initEvent('scroll',true,true);
        document.body.addEventListener('scroll',function(e){
           
            window.dispatchEvent(scrollEv);
            window.onscroll&&window.onscroll();
        });
    }
}

以上代码可以实现document.body.onscroll时,触发window.onscroll事件。不过,很可惜,测试时发现document.body,document.documentElement对象的scrollLeft,scrollTop属性无论怎么读写结果都是0(非iframe下也有这个问题)。对于,需要判断document.body.scrollTop的滚动加载来说,这是个问题。

造成,document.body.scrollTop和document.documentElement.scrollTop读写为0的原因恰恰是:

html,body{width: 100%; height: 100%; overflow: hidden;}
body{-webkit-overflow-scrolling:touch; overflow-y:scroll;}

其实,主要原因是:html,body{width: 100%; height: 100%; overflow: hidden;}

可以写一个长页面,然后加上上述的样式,你会发现,window.onscroll永远不会被触发。

继续观察,还会发现一个有意思的地方,如下代码

html,body{width: 100%; height: 100%; overflow: hidden;}
body{-webkit-overflow-scrolling:touch; overflow-y:scroll;}

.tt{position:absolute; left:0; top:1000px;}

window.οnscrοll=function(){

console.log(document.body.scrollTop);

}

setTimeout(function(){document.body.scrollTop=300},3000);

 

当sto触发scrollTop赋值时,window.onscroll事件会被触发两次,可以观测到的数字如下:

300

0

但是观察的情况是看不到网页有跳动,这说明一个问题,就是当html的样式被设置为height:100%; overflow:hidden;时,ios是强制性地让document.body.scrollTop值保持0,一旦外部js改变document.body.scrollTop值时,onscroll事件就会被触发,但是ios不会执行滚动而只是将值由非零改为0,这个时候因为document.body.scrollTop值的改变,window.onscroll事件再试被触发,这时,document.body.scrollTop保持在0,window.onscroll事件不被触发。这就是上面观测到的window.onscroll被触发两次的根本原因。

 

要改变上述情况的办法是,把html属性的overflow:hidden;去掉即可,因为body那里设置也height:100%; overflow:hidden; overflow-y:scroll;了,html的overflow:hidden;本身就是多余的,即:

html{width: 100%; height: 100%;}
body{width: 100%; height: 100%; overflow: hidden;-webkit-overflow-scrolling:touch; overflow-y:scroll;}

但是这种方法只能解决在非iframe环境下的问题,在iframe下,用上述样式,页面无法滚动。来分析原因,先在页面中加入

setTimeout(function(){
     alert(document.documentElement.clientHeight);
   },3000);

观察输出会发现,输出的数字远大于手机屏幕的高度,document.body.clientHeight和document.body.scrollHeight是一样大的,这表明了,html和body都没有滚动条存在。因为iframe是用iframe元素来表示原来用浏览器窗口的window,浏览器的window会强制document.documentElement.clientHeight为可见区域的高度,而iframe就没有这个限制了,它可以是大于0的任意值。解决方案目前是有一个,不过是我最不喜欢的一个方案,把body节点内的所有节点都放在一个div中,

all Nodes
,然后使用以下的样式:

html,body{width: 100%; height: 100%; overflow: hidden;}\
.DOMWrap{width: 100%; height: 100%; -webkit-overflow-scrolling:touch; overflow-y:scroll;}

 

js代码如下:

if(top!=window){
    //处于iframe中
    if(navigator.userAgent.indexOf('AppleWebKit')>=0){
        //ios系统,需要兼容一下window没有滚动条和不能滚动的问题
        var iosStyle=document_createElement_x_x_x_x_x_x_x('style');
        iosStyle.setAttribute('type','text/css');
        iosStyle.innerHTML='\
            html,body{width: 100%; height: 100%; overflow: hidden;}\
            .DOMWrap{width: 100%; height: 100%; -webkit-overflow-scrolling:touch; overflow-y:scroll;}\
        ';
        document.getElementsByTagName_r('head')[0].a(iosStyle);
        //因为给document.body.scrollTop赋值时,window.onscroll会被触发,所以createEvent可以不用了
       // var scrollEv=document_createEvent('HTMLEvents');
        //scrollEv.initEvent('scroll',true,true);
        DOMWrap.addEventListener('scroll',function(e){
           document.body.scrollTop=this.scrollTop;
            //window.dispatchEvent(scrollEv);
            //window.onscroll&&window.onscroll();
        });
    }
}

以上代码可以解决ios下的iframe滚动和iframe里的window.onscroll事件兼容问题。不过,还是希望可以不用到DOMWrap这个节点,直接在html,body上就可以完成的方式。--在ios5下不行,因为document.body.scrollTop永远为0

 

为了更深入了解iframe在ios和android下的显示行为,我写了一个单例来观测记录iframe的各位行为,以下是观测的记录:

http://blog.sina.com.cn/s/blog_86acf48e0102vhg7.html

通过这个观测记录的总结,竟然得出一个结果:通过createEvent可以触发window.onscroll事件,但是却没办法强改 document.body.scrollTop的值!所以带document.body.scrollTop判断的内嵌页会出问题(ios8还好,因为document.body.scrollTop会有数值0->x->0变动)。

虽然没办法做出一个完全兼容的iframe内嵌组件,但是,如果一开始就知道自己做的是一个内嵌页面,那么上述问题就可以解决了,只需要把document.body.scrollTop或window.scrollY改一下就可以了。像如果是使用一个DIV把所有元素包括进去的写法,只需要把document.body.scrollTop改成DOMWrap.scrollTop即可。

你可能感兴趣的:(iframe在移动端的限制及其应对策略)