讨论背景
众所周知,fixed元素在IOS下的表现是糟糕的,fixed元素在滚动页面中使用会出现各类奇怪的问题,在微信浏览器中使用就更甚(如:页面滚动,fixed元素与页面相互分离;页面滚动,fixed元素消失等)。这些表现过于离奇,显得没有逻辑,一时间很难找到对应的解决方案。
因此笔者决定从一个简单列表页出发,把遇到的各类奇怪问题都罗列出来,并探究其出现的缘由。以便在开发中规避这些问题。html
假定咱们的需求是作一个列表页,列表页的顶部放置一些「其余」信息,底部放置一个「建立」按钮,中间显示「项目」列表内容。
设计稿大概是这样。web

实现方案
根据需求,咱们分别制做了三种解决方案。分别是浏览器
- 利用fixed定位,将「按钮」放在滚动区「项目列表」外面,解决方案示例1。
- 利用fixed定位,将「按钮」放在滚动区「项目列表」里面,解决方案示例2。
- 利用absolute定位,将「按钮」放在滚动区「项目列表」里面,并用「项目列表」去填充它所占的内容,解决方案示例3。
分别在PC和IOS浏览器中运行这几个demo,咱们发现,这些demo在PC中的表现都是符合设计需求的。但在IOS浏览器中运行,就会各类出现各类的问题,分别对应这几个现象。微信
- 解决方案示例1:从「其余」内容区域开始触碰屏幕,进行页面滚动,「按钮」'脱离'页面内容区域。


- 解决方案示例2:从「其余」内容区域开始触碰屏幕,进行页面滚动,「按钮」消失了。




要解释这几个现象,咱们须要从颜色填充提及。布局
滚动填充的颜色
-
颜色填充示例1。动画
- 重点代码:在这个示例里面,咱们不对「项目列表」的高度进行限制,直接让内容在body中进行滚动。而后将body的背景颜色设置为橘红色。
- 操做:进入页面后直接向上拉动页面,拉动到不可拖动为止。
- 现象:咱们发现「项目列表」的绿色区域下面,显示了body的背景颜色橘红色。
- 说明:填充的颜色是能够定制的。
- 疑问:这个颜色填充的区域会不会是body的延伸呢?


-
颜色填充示例2。spa
- 重点代码:去除了Body的背景颜色,改为body的背景图片并进行平铺。
- 操做:同上一个示例。
- 现象:咱们发现「项目列表」的绿色区域下面,填充的依然body的背景颜色,而不是body的背景图片。
- 说明:填充的部分并不属于Body标签自己。
- 疑问:那若是咱们将body的背景颜色去掉,而在html加上呢?


-
颜色填充示例3。设计
- 重点代码:将body的背景颜色去掉。
- 操做:同上一个示例。
- 现象:此次填充的颜色是html的背景颜色。
- 说明:这再次论证了填充的部分并非固定某元素的内容,不是某个元素的延伸。并且说明系统找颜色是从滚动区域逐级往上找的,直到找到为止。
- 疑问:若是body和html的背景颜色都去掉,又会显示什么颜色呢?


-
颜色填充示例4。3d
- 重点代码:body和html的背景颜色去掉。
- 操做:同上一个示例。
- 现象:能够看到填充的是白色。
- 说明:默认的填充颜色是白色。


-
咱们再回到颜色填充示例1。code
- 重点代码:与示例1相同
- 操做:此次咱们在微信中打开,并改变操做方式,先上拉显示橘红色填充内容,再下拉显示微信的黑边(即显示顶部"此页面由XXX提供"文案)。再从新上拉,到不可拖动为止。
- 现象:本来下拉填充的橘红色变成了黑色。并且不管再怎么操做,都不会再从新显示回橘红色。
- 说明:微信内置浏览器修改了默认的颜色填充。


小结
- 滚动填充的颜色是可定制的。
- 滚动填充的内容并非标签的延伸,只会填充纯颜色。
- 滚动填充的颜色是滚动区域逐级往上找background-color肯定的。
- 滚动填充的颜色默认值为白色。
- 微信会修改滚动填充的颜色值。
IOS滚动回弹机制
咱们知道IOS是有滚动回弹机制的(即进行滚动时,滚动到最顶部或者最底部显示的一个回弹动画。咱们上面讲的滚动颜色填充就是这个机制的具体实现)前面解决方案示例1中遇到的问题(「按钮」'脱离'页面内容区域),就是因为这个机制引发的。
如今咱们先来探究一下,这个滚动回弹机制具体的运行过程是怎么样的。如下操做均在解决方案示例1下进行。
重点操做以下过程:
- 先将示例代码在IOS内置浏览器safari中打开。
-
用双指捏起整个页面(即相似于图片的缩小操做)。
- 现象:咱们发现,在页面是能够被缩小的。页面外部部分是纯颜色。
- 说明:有一个容器包裹着咱们的页面。这个容器一般用于窗口缩放的时候,填充颜色。

-
双指重复缓慢地捏起,放松整个页面。观察页面变化。
- 现象:当刚开始缩小页面时,外部容器的颜色与滚动填充索引到的颜色(粉红色)相同。
- 现象:当逐渐缩小页面时,外部容器的颜色将从索引到的颜色渐变到白色(这个颜色和咱们上面探讨到的默认填充颜色相同)。

- 先将示例代码在微信内置浏览器中打开,重复上面操做。
-
用双指捏起整个页面(即相似于图片的缩小操做)。
- 现象:外部容器的颜色变成了黑色,并且容器顶部出现了「此页面由 XXX 提供」文案。
- 说明:在微信下,为了显示「此页面由 XXX 提供」的提示语,微信本身重写了这个机制中的颜色,设置为黑色。

-
双指重复缓慢地捏起,放松整个页面。观察页面变化。
- 现象:外部容器的颜色在黑色和粉红色之间闪动。
- 现象:越是放松页面,闪动越频繁。
- 说明:微信重设颜色的机制和原生滚动回弹中缩小页面渐变颜色的机制相冲突。
- 说明:不管是缩小页面仍是恢复页面大小,微信都尝试将容器背景颜色设置为黑色。



这一系列的操做解释了这几个问题:
- 为何在微信中,先往上滚再往下滚动页面,颜色的填充会变成了黑色,而不是body的背景色。由于微信对外部容器的背景色进行了重载。
- 为何解决方案示例1中,「按钮」看起来'脱离'了页面。
由于微信对外部容器的背景色设置成了黑色,因此滚动到底部进行回弹的时候,页面内容和按钮之间的区域(即颜色填充区域)变成了黑色。而黑色给让一种"空"的感受,因此感受到「按钮」脱离的页面内容的错觉。
- 在safari中并不会改为黑色,即填充的颜色和Body的背景颜色一致。因此不会有黑色,不会产生微信上。「按钮」脱离的页面内容的错觉。
fixed定位基准值问题
在刚才的示例操做中,不知道你们有没有发现一个奇怪的问题。
在页面缩放过程当中,fixed元素与其余元素是在不一样的显示层进行渲染了?

从新执行前面的操做过程。咱们发现:
- fixed元素的定位并非基于手机屏幕,由于缩放的过程当中,「按钮」随着缩放进行了上移。
- fixed元素的定位也不是基于body元素的,由于从回弹机制来讲,「按钮」早已经脱离了body区域(红色框标记的颜色深粉红色块就是body的背景色)。

fixed元素的基准值,实际上是介于两者之间的一个显示窗口(相似于viewPort)。
这个显示窗口在不缩放的状况下,等于浏览器的窗口大小。
在缩放的状况下,显示窗口大概是这样子。

body内容超出了显示窗口就造成了回弹部分。
因此其实若是咱们往页面的左右部分滑动,也是有回弹效果存在的。只是这个手势操做被IOS写为页面「前进」,「后退」这两个功能罢了。
若是咱们将html的width设置为110%,当心滑动,就能重现左右的回弹效果。左右回弹示例


因此在页面缩放过程当中,「fixed元素与其余元素是在不一样的显示层进行渲染」的假像,
只是因为body相对于显示窗口同时在横坐标方向和纵坐标方向发现了位移,
造成了左右两部分的回弹颜色填充。
而fixed元素基于显示窗口固定,没有发生位移。而造成的分层的假想。
这解释了为何fixed元素为何一直在底部,而不是随着body在回弹机制下滚动。
IOS下position显示深度失效
最后这个问题最简单,也最离奇,在IOS中,除了设置z-index外,元素只根据元素在代码中出现的顺序决定其显示的深度。布局格式并不能改变元素的显示深度。
分别在PC端和IOS端运行布局示例。
在PC中,因为「按钮」fixed定位和「其余」absolute定位脱离文档流。因此其显示层级比「项目列表」高,因此覆盖在「项目列表」外面。

在IOS中,元素只根据元素在代码中出现的顺序决定其显示的深度。即:布局格式并不能改变元素的显示深度。
因此「项目列表」覆盖了「其余」和「按钮」。


这解释了最后一个问题(「其余」区域消失不见)。这是因为「项目列表」的padding-top将「其余」区域遮盖住了。至于为何要这么设计,后面会讲到。
也解释了第二个问题(页面滚动,「按钮」消失了)。
- 第二个问题是因为不改变显示深度,因此「按钮」仍处于「项目列表」容器内。
- 而「按钮」的定位是根据显示容器,而不是body,因此滚动过程当中不会跟着body移动,必定停留在底部。
- 在滚动时,「按钮」超出了「项目列表」的显示区域,「项目列表」设置了overflow,不在其显示区域的都不会被显示。因此滚动过程当中,「按钮」逐渐消失。
- 而多出来的部分是回弹机制填充的内容。因此产生了颜色遮盖了「按钮」的错觉。
常规解决方案
讲了这么多,那到底有什么方法能够规避上面的问题呢?
- 既然fixed布局这么多问题,咱们改用absolute布局吧。咱们改为absolute示例1。但屡次滚动后,咱们发现了另外一个问题,滚动时,「项目列表」的滚动会很容易与外部body下的滚动冲突。特别是当触碰到非「项目列表」区域的其余内容时(如「其余」或「按钮」),将触发的是body的滚动,没法滚动「项目列表」,违背操做意愿。

- 为了减小body滚动的触发概率,能够用「项目列表」的padding值对「其余」和「按钮」区域进行占位。获得absolute示例2。这时,即便从「其余」区域或「按钮」区域进行触摸滚动,滚动的依然是「项目列表」中的内容。不会触发body的滚动回弹。这便是一个好事,也是一个错误现象。由于在这个时候,移动「按钮」和「其余」区域。也会滚动「项目列表」。这和咱们的设计是不符的。

并且以上两个解决方案都还有另外一个操做的问题,若是当「项目列表」滚动到最顶部,或者对底部。中止1秒左右(即等待滚动趋势结束),
再往相同方向强制滚动(例如,往下滚到最低,静置1s,再往下滚)。这时,滚动回弹就不会在「项目列表」中进行,而是被放到了body区域上进行。这时在body的滚动趋势尚未结束前,不管怎么进行滚动。都会在body中触发。
与用户期待的滚动不符。
最终解决方案
上面的一切都是因为页面最外层的滚动回弹引发的。有没有方案禁止页面最外层的滚动回弹呢?很抱歉,笔者没有找到。
可是!笔者找到了不显示滚动回弹颜色填充内容的方法。
- 那就是fixed大法。直接将整个body设置成position:fixed。这时整个body都基于显示窗口定位。不会再显示滚动回弹内容。fixed示例1。然而这仅仅是不显示颜色填充的内容。滚动回弹其实仍是存在的,只是被body挡住了显示不出来。页面依然会存在上面的两个问题。最外部的滚动回弹仍是会与「项目列表」区相冲突。

- 怎么解决冲突?去掉「项目列表」的-webkit-overflow-scrolling: touch;样式,运行代码。fixed示例2。

- 冲突解决。不过滚动变卡顿了。怎么办?换个顺滑滚动的实现方案呗,例如用IScroll。fixed示例3
