手机网站在iPhone和Andriod的position:fixed替代方案

黄平俊 发布于 2012/11/29 14:39
阅读 12K+
收藏 2
问题背景
一个WebApp要做的像个App,多半会需要一个固定位置的头部、工具栏之类的效果,多半你会想到position:fixed;然后发现 哎呀 ,iOS的Safari上position:fixed;居然无效

解决问题前先来看看问题是怎样产生的:

Apple是这样解释的

    Safari on iPad and Safari on iPhone do not have resizable windows. In Safari on iPhone and iPad, the window size is set to the size of the screen (minus Safari user interface controls), and cannot be changed by the user. To move around a webpage, the user changes the zoom level and position of the viewport as they double tap or pinch to zoom in or out, or by touching and dragging to pan the page. As a user changes the zoom level and position of the viewport they are doing so within a viewable content area of fixed size (that is, the window). This means that webpage elements that have their position “fixed” to the viewport can end up outside the viewable content area, offscreen.

这里有个关键的东西叫做viewport,你经常在页面的头部里可以见到它:
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1.0, user-scalable=no" />

想起来了吧,就是它让你的页面不会像在桌面上那样显示,玩过windows的放大镜功能吧, 你可以把viewport想象成一个类似的放大镜,fixed的元素是相对整个页面固定位置的,你在屏幕上滑动只是在移动这个所谓的viewport,原来的网页还好好的在那,fixed的内容也没有变过位置,所以说并不是iOS不支持fixed,只是fixed的元素不是相对手机屏幕固定的。换个角度,如果所有fixed的元素都相对屏幕固定,那那些桌面版的网页在手机上还能看吗。



替代方案
弄清楚原因,接下来就是如何解决了,这里我参考了比较有代表性的两种解决方案。

jQuery Mobile

之前用这个框架做东西的时候就觉得它对fixed的处理很奇怪(demo),理解了上述viewport的模型后会觉得他的想法很简单也很自然,既然fixed的元素不会随viewport移动而移动,那就用相对定位让其随着viewport的移动改变位置,通过webkit的touch事件我们可以很方便的获取当前手指位置的坐标,据此计算偏移量并改变fixed元素的top偏移量,可以模拟元素fix在viewport的效果。

尽管touchmove事件可以让你随时获得当前偏移量,理论上可以做到平滑的滚动,但由于手指触发事件到浏览器完成渲染是需要时间的,如此模拟出的效果必定做不到理想中的fixed效果,更重要的是touchend事件后的scroll阶段,在这个阶段iOS会将DOM操作挂起(其API的ScrollEvent部分有说明),即会造成页面无法即时渲染。于是jQueryMobile就决定在整个viewport移动过程中(包括touch与scroll)让fixed的元素消失,scroll事件结束后再fadeOut。总之就是,很 奇 怪。

Sencha Touch/TWITTER

Twitter的处理方式则创新得多,效果也更加完美(后来发现Sencha Touch也是这样处理的),因为这个方案实现了真·fixed。所谓真·fixed,是因为在手指移动的过程中,不管是fixed元素的位置还是viewport的位置都没有改变。具体做法是监听body的touchmove事件,获取偏移量以改变内容元素的位置,并用event.preventDefault()阻止浏览器的默认scroll动作,来看代码:
<div class="tw-scrollview-host" style="min-height: 1018px; -webkit-transform: translate3d(0px, -22px, 0px);">....</div>

这里用translate3d只是因为仅有这个CSS属性iOS是调用硬件加速的…. 但是由于其阻止了浏览器的默认滚动,所以当touch事件结束后内容是不会惯性滚动的,于是又需要继续改变偏移量来模拟Scroll事件,这里就涉及到Scroll算法的问题了,要考虑手指移动的速度、阻尼的大小跟边界情况等等,我没有找到这部分的代码,也没有搜到任何相关文章,如果有人了解可以分享一下。

总的来说,Twitter Sencha Touch的方法在效果上更流畅,也更符合一般人的心理模型,如果硬要说缺点的话,我觉得这种实现太“不原生”了,几乎完全抛弃了移动浏览器的viewport模型,大兴土木地用CSS3与js模拟的scroll在执行效率与效果上与原生的差别也有待考究。

有个好消息:

iOS5即将更好的支持position:fixed;属性,加上android早在2.2就已经实现,以后要实现类似效果就不用再这么折腾了。


更好的消息:

不管用jQuery.mobile还是Sencha Touch ,这两框架都太重了,针对于国内的3G网络,下载框架+暄染页面都得等老半天,后来,我找着了了iScroll,它的实现原理跟Sencha Touch一样,效果流畅,还不卡,再后来,我用iScroll做了手机站:http://mm.lewole.com ,把平时喜欢的冷笑话和内涵段子收集在了一起~~~等公车,坐地铁时看一看,无聊时乐一乐~~~


加载中
0
Elkan
Elkan
position:fixed; 这个在Android平台上还是有问题, 例如滚动不流畅; 然后导致固定的元素 悬空之类的.
黄平俊
黄平俊
回复 @Elkan : 固定头部时,页面上用positioin:fixed是会有问题,所以才会有iScroll这个JS来代替position:fixed
Elkan
Elkan
@ideajava 你用的是 position:absolute, 例如滚动时 超过页面高度 需要固定页头时: position:fixed 则会有问题.
黄平俊
黄平俊
用iScroll4之后,就没有些问题了~~经测试iPhone4s和Andriod都滑动相当流畅,你可以用手机测试一下http://www.lewole.com
0
拥抱d
拥抱d

 

   
   
   

 

返回顶部
顶部