阅读 1174

2019再谈移动端position fixed

1.前言

最近在开发移动端页面的时候遇到固定顶部导航栏并添加搜索输入框的需求,第一反应就是用 position: fixed; 实现。但是这个属性在移动端总有各种奇怪的问题。早在2011年就有人问过这类似问题,但是都9102年了这个问题还有人提出。于是我决定在今年的新环境中再踩一遍 position fixed 的坑,提供更好的思路。

2.相关概念

  1. 浏览器窗口
    浏览器窗口就是浏览器中用来显示网页的可见区域,不包括工具栏的部分,但是包括水平滚动条和垂直滚动条 (只包括滚动条本身所占的像素,不包括已滚动的网页部分)。可以通过 window.innerWidthwindow.innerHeight获取。
  2. 浏览器视口
    viewport 就是浏览器视口,不包括工具栏的部分、水平滚动条和垂直滚动条。如果网页里没有滚动条,则浏览器视口和浏览器窗口的大小一样。移动端的css像素和屏幕物理像素并不一致,通常都会有缩放,这是在高分辨率屏幕普及后浏览器对网页做的兼容。开发移动端都会加上 meta viewport 控制缩放倍率。
  3. 堆叠上下文(Stacking Context)
    堆叠上下文是 HTML 元素的三维概念,这些 HTML 元素在一条假想的相对于面向(电脑屏幕的)视窗或者网页的用户的 z 轴上延伸,HTML 元素依据其自身属性按照优先级顺序占用层叠上下文的空间。

      下述 3 种方式目前都会使得 position:fixed 定位的基准元素改变:

    1. transform 属性值不为 none 的元素
    2. perspective 值不为 none 的元素
    3. will-change 中指定了任意 CSS 属性

      但是在不同浏览器中效果不同

      可以参考:www.cnblogs.com/coco1s/p/73…

3.曾经遇到的问题

  1. 不支持fixed
    在ios7和安卓2.3时代,移动端对 fixed 支持不好,有 bug 甚至直接不生效。此时只能依靠 absolute 模拟实现固定页头页尾。
  2. 在ios设备上,在fixed元素内有输入框,聚焦后弹出软键盘,fixed失效/奇怪的移动/输入框居中
    通常认为这是浏览器实现差异导致的。苹果为用户提供“人性化”功能:输入框自动居中使得safari有着奇怪的表现。
  3. 有的人会

4.测试环境

iphone 6s 

ios 13.1.3

safari

5.开始测试

1. 普通的 fixed 布局

查看demo


可以看到,在ios 13的safari上fixed的表现有点诡异,但是这也不像是fixed失效的样子。

于是我们再增加一个纵向的高度为一屏的彩色条

查看demo


这么看就很明显,fixed并未失效,只是相对定位的父元素并不是可见区域。


再看看安卓10上的表现,非常完美。

综合上面的 浏览器视口 概念,以及我后来进行的其他测试可以发现:

ios13在弹出键盘时,fixed也是工作的。只是 viewport 并没有对应缩小,也没有触发resize事件,而是在 viewport 父元素上再加了一层滚动层。再将input尽可能滚到屏幕中间。此时页首虽然也是fixed的但是viewport超出了屏幕。
安卓10弹出键盘时,fixed正常工作,viewport对应缩小,并触发了resize事件。此时fixed就符合我们的需求。

6. 解决方案

既然已经知道ios坑爹的设计,那么就可以针对性的设计解决方案。
显然在我们的代码中是无法获取viewport和真实屏幕间的滚动关系的,所以我们可以将计就计,定制一套输入组件。

  1. 原来的输入框只作为展示用,用户聚焦(focus)到输入框后,弹出fixed定位的全屏浮层
  2. 浮层的输入框放在顶部,输入框下面留空或者显示推荐内容、自动补全信息,提高用户体验
  3. 用户完成输入点击确定,浮层消失,内容同步回原来的输入框。

百度首页


这里直接拿百度移动版作为参考,如果业务简单,推荐内容和自动补全都可以省略掉。


7. 为什么不使用 absolute 方案

absolute定位方案已经得到了广泛应用,兼容性也是最好的。能兼容不支持fixed的浏览器。
但是到了今天,不兼容 fixed 的浏览器已经非常少了,因此

但是absolute方案也不是完美的,它有着另外一些无法解决的问题。

  1. 浏览器会记录页面滚动高度document.documentElement.scrollTop并在后退历史的时候让页面保持在原来的滚动位置。使用 absolute 方案会让document.documentElement.scrollTop始终为0,这个功能也就无法生效。(在ios13的safari上历史记录直接将整个页面运行状态都保存了起来,后退时甚至可以看到离开之前的状态而不是重新加载页面)
  2. 弹出键盘并没有改变viewport的高度,因此页面还是有可能会滚动(拖动fixed的页首就可以)。

查看demo



上面内容并不是我故意写成这样。实际上采用absolute的网站都有上面两个问题

pptr.dev/


这是Puppeteer的文档站,使用的技术栈相对先进。它采用了absolute方案,也没能避免被滚动的问题.

8. 总结

移动端开发相比pc端会有更多的坑,而且有时候为了用户体验并不能照搬pc的经验。移动端屏幕小,就更应该尽可能减少弹窗之类的设计,尽可能用全屏层或者直接开新页面。这样才能更充分的在有限的屏幕空间里展示最关键的信息。