别再用不规范的方法来解决软键盘遮挡输入框问题了

7,826 阅读22分钟

摘要:个人思考出来的创新技术方法来解决,舍弃网络广泛传播的不规范方法来就解决问题。并带你搞清楚问题的前因后果。

前言

此为经典移动端H5开发问题之一,做过移动端开发的人都知道会存在这种问题。而这类问题网上给予的解决方案也是十分大量,原本我也不想再写这种问题的文章了,但是从我实际搜索结果和自身实践体验来讲,效果不好,且不明所以,很多细节问题也没处理到位。

说白了,网上的这些资料我觉得不是一个最好的方案,它存在完善的地方,能让它更好。

此处为什么我仍然写这篇文章,原因有仨:

  1. 比起解决问题,我更想知其然知其所以然,求知欲促使着我
  2. 现存方案固然好用,然而并不是标准方法,存在风险性,我不愿意为后人埋坑,为公司埋坑
  3. 我想到更稳妥的方法解决拉,分享给大家(不然怎么诞生这篇文章 -_- )

为了方便大家接下来的理解,最好阅读我的另一篇基础文章——不如刷新认识移动端软键盘出现带来的网页问题 , 来了解下基础的知识

本篇文章能给你带来什么不一样的:

  • 很多文章只简单的把问题描述成遮挡住了输入框,殊不知,遮挡也有分几种,而且还跟H5布局有关,这就是为什么有些人用这些文章提供的方法还是不能解决问题的关键所在,不同原因造成的,一种方法并不能解决所有情况。
  • 切实地告诉你,出现遮挡的原因,以及解决的思路,并不是直接贴一段代码。而且很多文章大概说挡住了输入框,但是一些背景没说好,让人误以为跟自己遇到的问题是一样的
  • 很多资料都是二话不说就直接让自己写的代码生效,但是遮挡问题很多情况下不是必现的,但是大家却不管有没有出现,就直接采用自己的方案了,其实在利用本身应用智能滚动的条件下,是最好的,如果智能做的不好,我们才发挥自身的作用,我这里就提供一种判断方式
  • 要注意兼容性,还要就是符合w3c规范,很多都是直接用实验性的特性
  • 没有考虑好交互友好性

问题描述(原因分析)

那到底这里说的要解决什么问题呢?

移动端上,当页面有输入框,如input,textarea这些能触发手机软键盘的元素,弹出软键盘后,可能会出现软键盘挡住这些输入框的情况。

为什么出现这种现象?

当软键盘出现后,可视视口变短了,原本出现在可视视口的内容因为视口的变短本身就会导致部分内容被遮挡住,这是正常的表现。如果你要查看到这些被遮挡的内容,就要发生滚动查看。

正是因为这是个十分正常的表现,自然移动端应用开发者也意识到这个问题,所以他们也加以了帮助,帮助在可视视口发生挤压后,帮我们调节一下滚动容器的滚动距离,让聚焦的输入框能够展示在界面上。

然而这种辅助,并不能百分百准确,百分百处理得好。所以就有了这个问题出现的情况了。其实这种问题多尝试几次发现,并不是必现的,是偶尔出现。

正是这种不确定性,我们要做的,就是要自己切实控制得到,让当前激活状态的输入框能够出现在可视区域。

相对Android,IOS这种辅助会更加准确。此外,很重要的一点,这一点在ios原生的软键盘中是得到很好的调节,但是在第三方输入法的软键盘中,会出现计算有误差的情况,得不到很好地调节,滚动不到位,导致还是会出现被遮挡的情况。

总结一下原因:

  • 由于软键盘的弹出,只是压缩可视窗口了,却没调整相应视图的滚动距离,所以就会出现软键盘挡住输入框的情况了
  • 尽管原生有调整,也存在调整不到位的情况,特别是使用第三方键盘的情况下
  • 其实这里还有一种同样是输入框被遮挡的情况,但是却不是这里说的由于滚动不理想而导致的,这个情况就是我的前几篇文章里说的,IOS fixed样式失效,输入框是写在fixed元素上的,所以跟随fixed失效而导致的遮挡

探究

到底辅助功能,是怎么一回事。经过自己实际尝试验证,写demo验证,发现这么一个规律:

点击输入框软键盘弹出后,Android会依次从当前最顶层有滚动条的视图上进行调整,以让输入框能够展示在可视范围内,即如果最顶层滚动了还不足以显示,则滚动次顶视图的,依次自顶向下调整滚动; 而IOS的,则会先平移webview,当平移完发现还不足以展示,若webview自身也有滚动条,则也会滚动webview内容,底下的H5滚动容器不会触发滚动

正因ios的这种表现,所以会存在,输入框会被遮挡一部分的情况,不完全被遮挡住,因为假设scrollview出现了滚动条,而H5的内容也有滚动条,此时h5的滚动导致输入框只展示一部分,这时候出现软键盘不论scrollview怎么调整它自身的滚动条,也无法完全展示输入框,因为ios不像安卓可以调整h5的滚动条。

解决方案

思路(方向)

绝大部分情况下,聚焦出现软键盘后移动端会有个自动智能调节让聚焦的元素(输入框)出现在可视视口中。少部分情况下当然这个智能调节功能失效或不准确,此时,我们就需要进行人工干预调节,让其出现在可视视口中。

网上很多资料是直接不依赖应用本身的“智能调整”行为,而是不管三七二十一就自行调整了,很多都是直接在软键盘出现后,调用scrollIntoViewscrollIntoViewIfNeed方法,或执行一些自己编写的方法直接人工干预。

我们理应在智能调节效果不佳的情况下再进行人工干预,减少不必要资源的消耗,且重要的是,人家智能调节执行起来的效果还是比部分人工干预的方案执行效果体验更好。我们要保留这些优点,只是解决它的缺点就好了。

部分人工调节方案执行效果很直接粗暴,例如直接置顶等等,突然猛地一下页面变了个顶部位置,其实体验起来感受很差的。特别是软键盘出现后从一个输入框聚焦到另一个输入框,就会发现页面位置不断地在变化,很奇怪的。 —— 个人亲自体验感受

绝大部分网络资料要解决输入框遮挡问题的答案就是调用Element.scrollIntoViewIfNeeded()。是的,这个方法十分好用,会自动判断当前输入框是否被遮挡住,若是则进行调整。这样的话,其实我这边文章就不用写了,一个方法全都搞定。

然而很遗憾的是,它是一个非标准方法。

image.png

但是我敢说实在的,很多人才不管它标不标准,现在能用就行了,大家觉得当前能解决目前遇到的问题就好了,至于未来,可能并不关心。显然这种做法我并不赞同的,既然现在没有现成方法可用,自己造就好了。

因为是移动端,虽然说兼容性不太好,不过大体上还是可以用的,但是兼容性不是主要担心的,主要是这个方法还没写入规范中,以后也会有调整的可能性,用于生产环境可能有点风险。

显然谨慎的我,得想个兜底的方法,用可靠的方式方法来实现,而实现的前提,就是要先找出规律,能自己能够判断,输入框是否在可视窗口中。

那么问题来了,我们如何判断智能调节过后,输入框仍然被遮挡住呢?

判断是否被遮挡

我画了个图,方便大家理解其中的计算

image.png

图中有两个输入框均不在可视视口中,分别是输入框1,在可视视口下方,尚未被滚动浏览到;另一个是输入框2,在可视视口上方,已经浏览过了被滚动到上方了。

基于上述两种情况,看下两种情况时各种属性有什么关系,从这些关系中找到判断输入框不在可视视口的依据。

针对输入框1情况:

输入框1的getBoundingClientRect().top是大于可视视口高度的。

转换成代码:

inputElement.getBoundingClientRect().top > window.visualViewport.height

其实在安卓中,公式里的window.visualViewport.height最好换成window.innerHeight更为稳妥,因为window.innerHeight是个标准的老牌属性,虽然目前window.visualViewport.height的兼容性也挺好的。

针对输入框2情况:

输入框2的getBoundingClientRect().top是负数值,因为它被滚动上去了,是个负值。即 < 0

转换成代码:

inputElement.getBoundingClientRect().top < 0

因此我们只需要在智能调节之后作上述两种情况的判断,就能知道是否被调节好了,如果没有就要进行人工干预

人工调节

人工调节网上很多人除了使用上述的scrollIntoViewIfNeeded方法外,还有不少写着是使用Element.scrollIntoView()

说实话,这个scrollIntoView方法的变更让我印象十分深刻,相信对我未来的工作会产生强烈的思想影响。两年前我查阅该方法时,还是跟scrollIntoViewIfNeeded方法一样,是个非标准方法,然而现在一查,已经摇身一变成标准方法了。变成标准方法还不要紧,最重要的是,这个方法的含义,它所能做的事情也发生了变化,可想而知,曾经在项目里使用它的那些脚本,现在可能已经失效了。

正因为如此,更加坚定了我写这篇文章,寻找不能使用scrollIntoViewIfNeeded方法外的方案。

现在scrollIntoView()是标准方法了,某些场景下使用它也是很便利,例如H5容器作为页面的滚动容器(如div设置了100vh),又恰好是输入框的父容器,那么直接用是可以的。

要使用scrollIntoView()的条件比较苛刻,我不建议使用它:

  1. 它是滚动元素的父容器,但是很多时候往往父容器不一定就是页面的滚动容器
  2. 兼容性好的只有滚动到顶部或底部,其实我们往往想要的滚动到中间,然而支持滚动到中间的参数却有兼容性问题

不用现成的方法(因为没有好用的),我们必须自己探索自己编写人工调节的方法。

人工调节的目的是让输入框出现在可视视口,但是出现在视口什么位置呢?网上很多人会选择放在顶部,因为这是最简单的,但是同时体验也是最差的,无法看到上下文。

个人认为最佳的位置是放在可视视口中间,居中位置,其实移动端的智能调节正是尽量放在居中位置,如果实在不能居中,例如滚动到最大值了,无法继续滚动让其居中,这种极限情况就没法强求了。

仍然是这个图来进行分析,如何让聚焦输入框出现在可视视口居中位置

image.png

针对输入框1的情况:

先假定当前页面尚未发生任何滚动,即没有滚动距离。接下来分阶段让输入框1达到可视视口居中位置:

1)先让输入框1向上滚动到可视视口顶部位置。只需要将网页进行滚动输入框的getBoundingClientRect().top距离即可。

2)接着让输入框1向下滚动到可视视口高度的一半位置。只需要将网页进行滚动window.visualViewport.height / 2距离

PS: 安卓中建议使用window.innerHeight表示可视视口高度

3)第二步时已经差不多达到居中位置了,但此时是输入框的顶部对齐的是可视视口居中位置,而不是整个输入框,因此还需要向上滚动输入框自身高度的一半,以达到整体输入框的中间线达到可视视口的居中线位置。只需要将网页进行滚动输入框自身高度offsetHeight / 2的距离即可。

那根据上述的阶段分析,我们知道要滚动到居中位置,则要进行上述的各个阶段的滚动计算,那么滚动距离的计算转换成代码:

// inputElement代表输入框这个node节点
inputElement.getBoundingClientRect().top - window.visualViewport.height / 2 + inputElement.offsetHeight / 2

上述的计算是根据一开始假定页面尚未发生任何滚动下的,那么假设一开始页面就已经有一段滚动距离呢,则需要把这段滚动距离考虑上即可,因此最终的公式是:

// haveTop 为页面已经发生的滚动距离
// inputElement代表输入框这个node节点
haveTop + inputElement.getBoundingClientRect().top - window.visualViewport.height / 2 + inputElement.offsetHeight / 2

PS: 安卓中建议使用window.innerHeight表示可视视口高度

为什么我要用haveTop来表示页面已经发生滚动的距离呢?因为这个值在不同布局和移动端系统下是用不同的属性表示的。

在移动端布局中,常见的两种是

  • 不对H5页面设置任何高度限制,直接溢出内容,让webview出现滚动条—— 布局1
  • 对H5页面设置高度跟webview高度一样,当H5内容过多出现滚动条,是属于H5容器的滚动条 —— 布局2

如果看了我之前前几篇移动端分析的文章,对这两种布局的具体情况就比较了解了。

在安卓中,针对布局1,haveTop可用window.pageYOffset表示;针对布局2,haveTop可用H5滚动容器的scrollTop表示。

在IOS中,两种布局都是用window.pageYOffset表示,因为之前分析过,webview是可以平移的,只要在软键盘出现前界面上能看到要聚焦的输入框,那么软键盘出现后,不论布局怎样,只要平移webview必然能看到那个聚焦的输入框。因此只需要平移,即改变window.pageYOffset即可。

针对输入框2的情况:

思路基本跟输入1的情况一样,也是分为三个阶段

1)先让输入框2向下滚动到可视视口顶部位置。只需要将网页进行滚动输入框的getBoundingClientRect().top距离即可。

2)接着让输入框2向下滚动到可视视口高度的一半位置。只需要将网页进行滚动window.visualViewport.height / 2距离

PS: 安卓中建议使用window.innerHeight表示可视视口高度

3)还需要向上滚动输入框自身高度的一半,以达到整体输入框的中间线达到可视视口的居中线位置。只需要将网页进行滚动输入框自身高度offsetHeight / 2的距离即可。

转化成代码:

// haveTop 为页面已经发生的滚动距离
// inputElement代表输入框这个node节点
haveTop - inputElement.getBoundingClientRect().top - window.visualViewport.height / 2 + inputElement.offsetHeight / 2

haveTop的情况也是跟上述所说的一样的,这里就不赘述了。

实现

有了上面的各种分析,得到的各种计算公式,基本上方案就已经成型了,接下来只需要把各种信息汇总在一起,得出最终的实现方案。

当然在具体实现过程中,少不了有各种注意细节,这些细节往往能决定成败吗,不处理好这些细节,就算有主要核心方案,也变得无法施展。

判断软键盘出现

因为要判断软键盘出现后输入框是否被遮挡,那么前提就是要知道,怎么判断软键盘出现了。

很遗憾,目前没有现成的事件表示软键盘出现。只能退而求其次,根据表现寻找方法。对于软键盘出现的表现,安卓和IOS系统还不一样。

对于IOS来讲, 聚焦了输入框,软键盘就会出现,失焦输入框,软键盘就会消失。软键盘的出现与消失,必然也会反映到输入框的聚焦与失焦。即它们是一一对应的关系,聚焦必软键盘出现,失焦必软键盘消失。

基于上述表现,完全可以监听输入框的聚焦(focus)与失焦(blur)事件来判断软键盘出现与否。

这里建议采用focusinfocusout替代focusblur事件,因为前者支持冒泡监听,可用于事件委托

对于安卓来讲, 聚焦了输入框,软键盘就会出现,失焦输入框,软键盘就会消失。但是如果点击软键盘上的诸如“完成”“收起”等软键盘上的按钮,软键盘就会自己收起来,然而界面上输入框还是出于聚焦状态的。再次点击输入框,软键盘也是会出现。

基于上述表现,就无法采用监听聚焦失焦来代表软键盘出现与否,但是每次软键盘出现,意味着webview变短了,即会触发resize事件。但是它们之间并不是一一映射关系,即虽然软键盘出现会触发resize事件,但是触发resize并不表示就是软键盘出现了。

因此还要进一步缩小范围,虽然没办法做到100%让resize事件代表软键盘出现,但是本身移动端触发resize事件的场景就很少,我们加多点判断范围,基本能锁定是软键盘出现引起的就好。

移动端中会引起页面resize的场景有哪些?

  1. 手机方向的更改,也会触发orientationchange事件
  2. 部分浏览器,在页面滚动时,地址栏或工具栏会发生缩放等微小变化,这也会引起resize
  3. 人为的缩放页面(如网页自身提供缩放页面大小的产品功能等),这种就没办法啦。
  4. 等等(不知道还有啥,欢迎补充)

我们要筛选掉第2种场景,第1种场景不用筛选,因为翻转后也有可能被遮挡

对于第2种情况,基本上浏览器这些细微变动不会很夸张的,一般也就0到几十px的变动范围,而软键盘的出现,window的resize量是很大的,因为你看软键盘的高度都占那么多了,因此我们可以从变化量上做一定筛选,基本我看了下,软键盘高度大概在200-400px之间,因此在监听resize的时候,计算下变化量,大于200px的才视为软键盘弹出就够了,基本页面上除软键盘弹出外不会有这么大的变动的。

结合上述,转化成代码

// ios情况下
if (/iphone|ipad|ipod|ios/i.test(navigator.userAgent)) {
    page.addEventListener('focusin', focusinPage)
} else {
    page.addEventListener('click', handleClickPage, true)
    window.addEventListener('resize', handleAndroidResize)
}

可以看到在安卓上,绑定了两个事件,一个是页面的点击事件,一个是windowresize事件。

为啥要监听点击事件,主要是为了知道当前激活的是哪个输入框,那个当前激活输入框对象,为了后续的人工调节计算公式所用。当然,在IOS的聚焦事件中,同样也是会找到激活的输入框对象。它们都是采用了事件委托来实现监听。

具体绑定的方法,见下方

人工调节触发滚动的方法

各种判断都已经准备就绪了,最后只要经过上述分析的各种判断后,满足条件就可以人工介入进行调节滚动了,那么问题来了,用什么方法触发滚动好呢?

我这里罗列出一些可能大家心目中的答案:

  • Element.scrollTop
  • scrollIntoView()
  • scrollIntoViewIfNeed()
  • scrollTo() / scroll()

实际上还有更多方法。那么具体选用哪个最好呢?

大家平常使用的更多的是直接改变滚动容器的scrollTop属性,但是window对象没有这个属性,但是如果window本身就是滚动容器的情况下,就无法通过改变该属性来让window发生滚动了,因此排除掉。

对于scrollIntoView()scrollIntoViewIfNeed()方法之前分析过了,这里不赘述了。

最终我们选用的是scrollTo()scroll()实际上它们是一样的,任意使用一个即可。它能用于在给定的元素中滚动到某个特定坐标。即我们只需要把我们的计算结果直接丢到方法里使用即可。

scrollTo()scroll()支持window对象和Element对象调用,故能很好的针对布局和系统做变化处理。在IOS中不论什么布局,直接使用window对象进行调用,在安卓中,根据布局情况,若是H5容器作为整体页面的滚动容器,则使用该容器对象进行调用,否则也是使用window对象。

需要注意的是,如果安卓中存在多层滚动容器嵌套,如页面组成结构比较多层嵌套,例如body > div1 > div2 > div3。这三个div都是滚动容器,且它的高度并不是等于可视视口高度,而输入框恰好在div3上,而div3恰好需要div1 和 div2滚动才能看到,在这种多层嵌套滚动的情况下,这篇文章所使用的方案是行不通的,无法准确知道需要滚动哪个容器到哪个位置才能让div3的输入框显露出来。这种场景实在太少了,一般来讲移动端布局不会很复杂,多层滚动嵌套的情况很少,再加上经过智能调节后还不成功的,概率更加少上加少,因此这种极少数场景,我这里不想去探究

核心代码分析

先分析安卓端。

监听了点击事件,记录当前激活的输入框对象,这个比较简单

function handleClickPage (e) {
    var el = e || window.event
    if (/^input$/i.test(el.target.tagName)) {
        currentInput = el.target
    }
}

接下来主要是监听resize事件,结合前面所述的判断智能调节后仍然被遮挡的情况下就要执行人工调节的核心代码。

主要分成三部分

  • 判断resize的变化量是否比较大,视为软键盘弹出
  • 判断智能调节后是否仍然被遮挡
  • 遮挡了就进行人工调节滚动
function handleAndroidResize () {
    var resizeHeight = lastInnerHeight - window.innerHeight // 本次变动量
    lastInnerHeight = window.innerHeight // 记录目前的可视窗口高度,以便下次计算resize变动量
    // 弹出软键盘
    // 页面resize变化量大于一定程度,如200,则视为是因软键盘的弹出导致的resize
    // 基本上移动端上resize事件无非就是软键盘、浏览器各种“栏”在滚动时的变化、或者旋转方向,如果你的产品还有其他会导致resize的场景,请谨慎使用该方案。
    if (resizeHeight > 200 && currentInput) {
        setTimeout(function () {
            // 第二个参数,如果当前布局结构是页面容器设置了高度和可视视口高度一样,滚动条是属于这个H5容器的而不是window
            // 则第二个参数要传滚动容器的dom对象,如 document.querySelector('.page')
            calcScrollTop(window.innerHeight, window)
        }, 150)
    }
}

可以看到上面代码中加了个定时器,为什么要加个定时器,是因为软键盘出现了触发resize,还需要等着时间让浏览器自动调节滚动,调节完后才轮到我们判断是否智能调节得不够

其实定时器里的代码相当于浏览器智能调节,实践了下,效果是一样的,所以你也可以直接不加定时器,直接执行里面的代码,相当于覆盖掉智能调节,以我上面这段代码作为智能调节一样

它应该是这么一个过程:

  1. 首先软键盘出现,window单纯的被压缩,
  2. 接着判断被压缩过后目标输入框是否不在可视视口中
  3. 若不是,则进行滚动,否则维持现状。

从此时resize事件中的各种参数值的情况来看,此时应该处于第2阶段,可在这里预测判断即将是否在可视视口中,而不是到了第三阶段的智能调节完毕

这个秒数是经过多次试验得出的一个较好的数值,能在智能调节后立马人工干预,不会说停顿太久突然干预产生奇怪的体验。如果这个定时器你觉得不好,其实你也可以去掉,直接不需要智能调节,人工调节好,但是有种情况不能去掉,如果页面组成结构比较多层嵌套,例如body > div1 > div2 > div3

代码里的calcScrollTop方法,是封装的方法,里面主要是把判断是否输入框被遮挡以及进行人工滚动的代码,具体见下方的完整代码

IOS的情况更为简单。

因为IOS要复现这个问题比较难(偶现),无法像安卓那样推断出在执行focusin事件时各属性参数处于智能调节前还是后。为了确保效果,就写在定时器里,让软键盘出现页面完成智能调节后,再去判断是否被遮挡住然后再人工干预调节

function focusinPage (e) {
    currentInput = (e || window.event).target
    setTimeout(function () {
        calcScrollTop(window.visualViewport.height)
    }, 300)
}

完整代码

html部分

<body>
    <div class="page">
        <div class="wrap">
            <!--这里面有输入框-->
        </div>
    </div>
</body>

脚本部分,注意在handleAndroidResize的定时器中调用calcScrollTop方法的第二个参数,要根据你的页面的布局情况来决定。

var page = document.querySelector('.page')
var currentInput = null // 当前聚焦的输入框
var lastInnerHeight = window.innerHeight

function calcScrollTop (visualHeight, scrollObj) {
    scrollObj = scrollObj || window
    var currentInputTop = currentInput.getBoundingClientRect().top
    // 已经偏移(滚动)的量
    var yOffset = window.pageYOffset
    if (scrollObj !== window) {
        yOffset = scrollObj.scrollTop
    }
    // 不在可视视口中
    // 被软键盘遮住
    if (currentInputTop > visualHeight) {
        content.innerText += ' 软1'
        scrollObj.scrollTo(0, yOffset + currentInputTop - visualHeight / 2 + currentInput.offsetHeight / 2)
    // 滚上去了可视视口,被视口遮住
    } else if (currentInputTop < 0) {
        content.innerText += ' 软2'
        scrollObj.scrollTo(0, yOffset - currentInputTop - visualHeight / 2 + currentInput.offsetHeight / 2)
    }
}

function handleClickPage (e) {
    var el = e || window.event
    if (/^input$/i.test(el.target.tagName)) {
        currentInput = el.target
    }
}

function handleAndroidResize () {
    var resizeHeight = lastInnerHeight - window.innerHeight // 本次变动量
    lastInnerHeight = window.innerHeight // 记录目前的可视窗口高度,以便下次计算resize变动量
    // 弹出软键盘
    if (resizeHeight > 200 && currentInput) {
        setTimeout(function () {
            // 第二个参数,如果当前布局结构是页面容器设置了高度和可视视口高度一样,滚动条是属于这个H5容器的而不是window,如例子中的div.page,
            // 则第二个参数要传滚动容器的dom对象,如 document.querySelector('.page')
            calcScrollTop(window.innerHeight, window)
        }, 150)
    }
}

function focusinPage (e) {
    currentInput = (e || window.event).target
    setTimeout(function () {
        // 第二个参数采用默认的window,在ios中不论页面布局怎样,只要软键盘出现前已经出现过可视视口的,那么软键盘出现后必然能通过webview平移看得到
        calcScrollTop(window.visualViewport.height)
    }, 300)
}

// ios情况下
if (/iphone|ipad|ipod|ios/i.test(navigator.userAgent)) {
    page.addEventListener('focusin', focusinPage)
} else {
    page.addEventListener('click', handleClickPage, true)
    window.addEventListener('resize', handleAndroidResize)
}

另类的遮挡

这里所说的,表现同样是输入框在软键盘出现后被遮挡了,但是它的原因却跟上述说的不一样,因此解决办法也是截然不同。

很多人在网上查资料或写博客,忽略了一些背景的说明,虽然是找到了某个方法,但是自己应用的时候却不能生效,不是别人的方案不行,只是问题出现的时候,很多背景因素不同,例如布局不同,样式不同,操作不同甚至浏览器不同等,都会导致处理手段不能顺利解决。

一般移动端应用都会在页面底部有个导航栏或操作栏,是固定在底部的,一般就是用position: fixed定位。当软键盘出现后,会发现聚焦的输入框看不到,其实只是单纯地就是被这些fixed元素遮挡,如果我们摘掉这些fixed元素会发现,其实输入框已经出现在可视区域了。

因为内容滚动的计算容器尺寸什么的,都不是把这个fixed的元素所占空间计算在内,这是基本和正常的认识。即智能调节不会考虑到fixed定位元素的,此时可能是正确的,输入框确实是出现在可视视口的,只是被层级更高的定位元素遮挡住而已。

解决的思路也是十分简单,就是当键盘出现后,fixed部分的元素的就要回归到正常的文档流(如设回position: static),收起键盘后重新变回fixed元素。

这是一个整体上的思考方向,当然很多细节可能会受到你具体的页面布局影响,甚至这么改变会影响一些交互,具体问题具体分析,大方向的实现上是很简单的。

有了上面处理的经验,要解决这里的问题易如反掌,主要还是分为几步:

  1. 判断IOS还是安卓,安卓采用resize判断软键盘出现,IOS采用focusin判断软键盘出现
  2. 软键盘出现后设置 element.style.position = 'static';软键盘收起后设置element.style.position = 'fixed'

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

本文正在参加「金石计划 . 瓜分6万现金大奖」