最近在开发手机版邮件系统,产品经理提出邮件列表页面菜单栏要固定手机底部,最直接想到的是position:fixed这个属性,但是发现在iphone中并不支持这个属性,菜单会随着滚动条滚动而滚动。后然就想到通过脚本控制来解决(参考jquery mobile),通过touchstart,touchmove和touchend事件来计算clientY变化来相应移动菜单,但是还是发现有两个问题,如果移动的快的话:1.菜单无法及时定位有延迟2.在iphone中touchend事件后还会scroll,看了jquery mobile解决办法它是先隐藏相应菜单,等算好了再显示,总觉得怪怪的,而且这个办法最后还是被项目经理给no了,其实一开始就想到iscroll.js这个框架觉得它蛮不错的。一直有关注它,但是在4.2.5之前版本连内容中A标签都点不了,所以就没想用它,到最后没折了就去蛮试了一下,想不到4.2.5解决很多之前的bug,最后还是用了它来解决。同时也参考了163手机版的做法,采用以下解决办法:
1.在android手机中继续使用position:fixed这个属性;
2.在iphone中使用了iscroll.js框架来解决;
在网上找了一些资料觉得这篇文章写得很透彻,粘在自己下面收藏一下:
问题背景 :
一个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就已经实现,以后要实现类似效果就不用再这么折腾了。