不如刷新认识移动端软键盘出现带来的网页问题

7,030 阅读13分钟

前言

这篇文章能给你带来什么?我相信你们也简单了解过webview在软键盘出现前后的变化是怎样的,但是我相信你阅读过我的文章之后,会有另外一番收获与认识。

关于移动端软键盘带来的影响,做过移动端开发的都知道有很多经典问题,例如输入框被软键盘遮挡、fixed元素不生效、IOS布局错乱等各种经典问题。

心想着,这么经典的问题,想必有很多学习资料解决方法啥的,可惜,很多资料要不就是解释不到位,要不就是直接接着摆个解决方法没有分析,要不就以讹传讹。

出于无奈,决心自己去好好屡一下这方面的知识,好好总结,并思考一些问题的解决方案,对比目前已有的方案的不足,想出更好的解决方案。

根据自己钻研结果,考虑到篇章问题,决定分开写出几篇文章,逐步去认识软键盘带来的影响以及如何解决这些问题。

理解webview表现

一切的一切,首先必然离不开软键盘出来前后,有啥变化,而这些变化,正是后面各种问题产生的“元凶”,所以你要解决那些问题,必须得知道,问题产生的原因是什么,而我们去理解这些变化,变明白了原因。

首先我们得先对手机webview等情况要有一个大概的认识,知道,我们的H5页面在移动端的webview是如何表现的。

在app中,有一个叫webview的玩意,是用来展示H5页面的,实际上你可以把它当成是一个浏览器,所以H5页面的一些属性是跟随webview来的,例如window.innerHeight就是webview的高度。

说白了你就把webview看作一个浏览器,跟在网页端运行在浏览器上的情况一样,只是浏览器的载体不一样而已。

同样地,如果网页的实际内容很多,超出了webview的可视区域,则就会出现了滚动条。

但是要知道,这个滚动条是属于webview这个“浏览器”的,已经不是H5的范畴了,我们用css是控制不了的,是应用的原生滚动条。这点是跟web端真正浏览器的情况不一样,在web端浏览器如webkit内核的浏览器我们是可以通过-webkit-scrollbar来控制样式

在安卓部分浏览器上能看到这种浏览器对比差异效果,如使用css样式让滚动变粉色,会看到webview上的滚动条是不受影响的,但是H5的容器的滚动条生效,见下图gif;但是IOS中,尽管是webkit内核的还是无法通过css控制H5的滚动条样式。

移动端滚动条.gif

从上图Android端gif中可以看到,我用css全局把滚动条变成粉色了,最右边边有个浅浅的灰色滚动条,那便是webview上的滚动条,我无法用css控制,图里的粉色滚动条便是div上的一个滚动条

这里也仅分析了一个软键盘出现前的情况,因为软键盘出现后,涉及到Android和IOS的差异了

Android和IOS的webview表现差异

Android

安卓的情况比较好理解。软键盘弹出后,实际webview被挤压了,变短了,相当于浏览器变小了,变成 原本高度 - 软键盘高度

image.png

表现正常浏览器显示效果是一样的,只不过是webview“浏览器”变得很小而已。

IOS

iOS 发布了 8.2 版本之后,与androids的不同,软键盘弹出,并不是挤压webviewwebview的高度不会发生变化

而软键盘更像是一个悬浮在weview上的东西,并不会影响webview的实际高度,是“盖”上去的。

这是开发团队故意为之,为了不用重新渲染视图,造成消耗,然而这种优化,带来了更多的负影响。

image.png

而如果就这么直接把软键盘“盖”到webview上,显然会遮挡位于较底部的内容,如果网页内容本身也不多不至于让webview产生滚动条,则被遮挡的这部分内容就无法查看了,显然IOS的开发者也意识到这个问题,所以他们用了一个让web开发者头疼的解决方案——webview整体发生移动,以让遮挡的部分能够被展示出来。它能被滑动的最大距离为软键盘的高度

所以可以看到,我们在IOS上聚焦某个输入框,是会自动让页面发生滚动,以展示聚焦的输入框,此时页面的滚动,实际上是webview自身发生了移动。

但是这种自动的移动,并不完全是准确的,可能会受到各种方面因素的影响到导致其移动位置不够精准。这就是IOS端上可能发生输入框被遮挡的原因。

webview最大能被向上移动的距离为软键盘的高度,这样就会导致一个问题:就算网页内容很短,本身不足以引起网页发生滚动,但是在软键盘出现后,webview是可以被移动的,则仍然可以发生滚动,尽管底下是一片空白,同样也会让部分内容被滚动上去了。

滚动多了.gif

如上图,网页内容要给固定顶部的元素以及一个输入框,网页内容是很短的,不足以引起webview产生滚动条,本身是不可滚动的。但是软键盘出现后,由于webview可以移动,导致就算底部是没啥内容的,也是能发生滚动行为。

如果网页内容过长(不对html, body设置heightoverflow),本身webview也是能够滚动查看其内容的,再加上软键盘的出现后,让webview自身也能滚动,所以此时就会有两种滚动,我们在移动端上滑动屏幕时,会先滚动webview“浏览器”自身,滑动到最大距离(软键盘的高度),则后面会滑动webview“浏览器”自己的内容。不论上滑还是下滑,都是先移动webview自身,再到H5页面自己的内容。

safari这里就有点坑了(还好意思说是原生自带浏览器,我晕,问题更多 - -),它在移动完webview自身和webview内容后(不论有没有内容可以滚动),还可以继续向下滚动一段距离,经测试发现,这个距离就是safari底部工具栏的高度。所以当软键盘出现后,一致滚动到底,总会看到有一部分空白区域,我们的H5无法描绘到的区域,就显得很奇怪了。我的推测是,软键盘出现前就腾出了底部工具栏的空间了,所以由于软键盘出现后页面没有重新渲染,仍然保留了那部分腾出的空间,导致页面“被增高”了。同样是底部有工具栏的chrome,然而没有这种问题,应该是浏览器具体实现的差异导致的。

虽然这实际上是两种滚动,但是不论从表现上(我们肉眼上看滚动效果),还是从js脚本里反馈(典型的是window.scrollY属性值)来看,都是一致的。

就算webview自身发生移动,也是会影响window.scrollY的值的,所以你无法从js反馈的属性值里判断,当前是属于哪种滚动,反正我没找到这么一个属性能够让我知道,,ԾㅂԾ,, 但是!原本我还考虑IOS13以上的,可以使用window.visualViewport来计算webview可自身移动最大距离的,结果可能还会存在一些问题,特别是safari上。

为什么说这种方案让web开发者很头疼呢?我们知道position: fixed;在web端上,是固定于浏览器视口上进行定位的,不管怎么滚动内容,都是会固定在浏览器视口指定位置上了。而现在把这种布局放在IOS移动端上,则会失效。

因为上面说了,fixed布局是固定在浏览器视口上的,而在移动端这里,浏览器就是webview,即相对于webview固定的,而软键盘出现后,由于是先让webview自身发生移动,则如果移动了,那么自然fixed到上面的内容也可能随着webview的移动被移出可视视口了。

这就是牵涉出另外两个经典问题了:1)固定到顶部的内容在IOS上不生效;2)固定到底部的内容在IOS上不生效,或不能挨着软键盘顶部。前者是因为webview移出了可视视口,后者是因为被软键盘盖住了

正因为这样,在IOS上的这种行为,会衍生出各种问题,正让web端开发者面临着挑战。

注意:

这里还有值得一提的是,上面说了如果webview自身内容能发生滚动,在软键盘出现后,做滚动手势会先平移webview后滚动webview自身超长的内容。要注意上述表现的前提是webview自身内容超长有滚动条。

上述说的是webview上有滚动条,而不是H5容器的滚动条,一般不对html,body设置跟webview一样的高度,只要body上的内容过长超出webview的高度,就会出现webview上的滚动条。

而如果我们对html, body等设置页面内容设置高度跟webview一样,则webview没有滚动条,如果body里的某个H5容器是能滚动的,则软键盘出现后,我们作滚动手势,是先滚动H5容器后平移webveiw的。

就会好比 div1 > div2 > div3,他们仨都有滚动条,当我们滚动时,显然是先滚动div3,div3滚动到极限了,再滚动就是滚动div2了。

上面是一个大概的介绍了解,下面我通过具体的一些例子来进一步帮助大家理解一下。

例子一

H5内容上不设置高度,页面高度随着内容的增长而增长,如html,body上不设置height,直接body的高度随内容增长。

软键盘弹出前,如果网页高度没有超过webview高度则没有滚动条,若超出了,则出现滚动条,是webview上的滚动条

软键盘弹出后,

在Android中,webview缩小了,自然H5可视区域高度也缩小了,若H5实际内容长度不超过webview,则webview上不会出现滚动条,否则出现。

而在IOS中,则不一样,webview高度不变,IOS为了让能看到webview的全部内容,会有滚动条支持滚动平移webview,平移达到最大距离后,若网页内容本身也是超过webview高度的,就会继续由webview上的滚动条支持滚动网页内容。

这就是假设你拿台IOS机子来测试一下,你会发现,就算网页的实际内容看起来很短,但是为啥软键盘出现后,仍然有个滚动条可以往下滚动,一片空白,这是因为平移webview

所以说白了,IOS中,不论页面布局怎样,软键盘出现后,肯定有一个滚动条,即scrollview的滚动条。

这里以GIF来看下,如果你的页面内容很长,超出了webview,甚至超出了软键盘的高度,那么你会无感知到webview的平移和webview内容的滚动。

为了更好的区分出来weview的平移和webview内容的滚动,我这里在页面底部增加了一个position:fixed的元素。

IOS.gif

我们可以看到,该页面为一个长页面,没有对body、html那些元素设置高度的,页面实际内容超出了手机webview可视窗口高度,因此weview上肯定存在一个滚动条,是滚动页面内容的。

当聚焦输入框,出现了软键盘后,向下滑动,此时是先平移webview的,为什么能确定这么说的,这时那个fixed元素是个很好的参照物

先说一下fixed元素是参照webview定位的,这里的fixed元素是固定在webview底部的。

GIF中看到,软键盘出现后,fixed元素看不到了,这是被软键盘盖住遮挡住了,① 然后慢慢向下滑动,看到fixed元素渐露,② 最后再怎么向下滑动,就固定在软键盘顶部了。

在第①步中,是因为此时滑动的是webview自身,所以固定在webview底部的fixed元素由于webview慢慢被平移上去了,所以跟随webview一起平移上去,直到webview平移到最大距离(软键盘高度),fixed元素被完全露出来。

在第②步中,由于webview不会再被平移了,但是页面内容还有很多没展示出来,继续下滑就会触发webview自身内容的滚动(你可以理解为浏览器自身内容长出现了滚动条,在滚动查看)

对于GIF中后半部分,改成上滑,道理是一样的,一开始上滑,是先触发webview自身的平移,因此看到fixed元素渐渐消失了,后面直到webview平移到最大距离后,继续上滑就是滚动webview中的页面内容了。

例子二

H5内容上设置了高度,如html,body设置了height: 100%,或者常见的body底下的子div设置了100vh,这种情况都会限制了H5的高度,等于window.innerHeight,即等于webview视图的高度,,这个很关键!

vh和window.innerHeight是跟webview高度保持一致,即在安卓中,会随着软键盘出现而变小,而在ios保持不变

软键盘弹出前webview, H5的可视高度都一样,如果H5里设定了高度的容器(如body)内容的高度超过自己的可视高度(此时的clientHeight=window.innerHeight),则很自然这个容器就会出现滚动条,此时的滚动条是属于前端范畴的,虽然界面上看起来,可能跟例子一说的webview的滚动条很相似,但是要知道,这不是同一个滚动条。这里前端的滚动条可以用样式来改造一下凸显一下不同。

关键来了,软件盘弹出后

跟例子一一样,在Android中,跟弹出软键盘前变现一样。但是在IOS中,首先,webview上必然会出现滚动条,接着,就是看H5底下的内容是否超过H5自己容器的高度了,超过了就会出现H5自己的滚动条。

所以此时会有俩滚动条,只是ios滚动条在不滚动时就会隐藏,很多人都没能注意到,在滚动时,到时候是哪个容器的滚动条,这个我们自己要清楚。

IOS2.gif

结尾

看到这里,不知道看官是否刷新了自己对这块的认识。如果是的话,麻烦留言一下,扣个1,让我感受到各位的认同。

此为第一篇,后续就这种本质原因导致的各种问题,进行一个分析和解决方案的梳理。

全部内容均为鄙人不断学习与实践得出的,虽然鄙人的各种文章话题性都是较老的,老生常谈的话题,但是往往能带给你不一样的理解与感受,正因为这些话题存在不足,可弥补性可增强性,我才愿意研究它并分享出来。如有错误欢迎大家指出共同学习,欢迎大家的关注。

文章未经授权,请勿私自转载