如何解决移动端 input 框软键盘弹起导致 header、footer 错位问题

发布于 2022-11-18  69 次阅读


摘自:https://juejin.cn/post/6961757804491178014

问题

问题描述:

在ios手机中,当页面中包含有输入框时,点击输入框,键盘弹起,会让页面中被fixed的元素失效。所以造成了底部吸底和顶部吸顶的元素错位的问题。下面的视频中就出现了这个问题,吸顶元素被推到可视区之外去了,而吸底元素也被推到了键盘之上。

Gif 如下:

https://cdn.nlark.com/yuque/0/2022/webp/260235/1649689642339-c6ab1220-e6e8-4d2c-8088-6509ccb3997d.webp#clientId=ue4253c9d-3936-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown error&from=paste&id=u56086508&margin=[object Object]&originHeight=1792&originWidth=828&originalType=url&ratio=1&rotation=0&showTitle=false&status=error&style=none&taskId=u3b797abb-aff4-4a9b-85be-a58e9059b44&title=

ui希望优化的点:

一开始,ui针对这个视频中出现的问题,提出了3个优化点: 1、希望吸顶元素能够继续吸顶 2、希望吸底元素能够继续吸底 3、希望当键盘弹起之后,输入框能够保持在键盘之上48px的距离

最终决定优化的点:

经过一番调研,在我搜集到的可行方法中,结合有限的时间因素,在和ui协调之后,将这3个优化点变成了下面这3个优化点。同时还参考网上文章,增加了一些特殊情况下可能出现问题的优化点。 1、吸顶元素能够继续吸顶 2、吸底元素(也就是按钮)能够在键盘弹出之后,出现在键盘的上方 3、键盘弹起,输入框出现在可视区内。(对于这点,ios本身是支持的,但是安卓却并不会主动让输入框出现在可视区域内) 4、对于部分ios系统下的部分微信webview内,发现软键盘收起时,滚动上去的页面没有滚动下来,造成了下面区域留出了一片灰色的区域。

先要弄懂的问题

解决这些问题之前,需要弄明白以下2个问题:

1、当键盘弹起来的时候,会发生什么

这里ios和安卓系统下表现的并不一致。这个参考了朱雷大佬提供的这个文章:WebView上软键盘的兼容方案

⚪️ IOS 软键盘弹起表现

在 IOS 上,输入框(input、textarea 或 富文本)获取焦点,键盘弹起,页面(webview)并没有被压缩,或者说高度(height)没有改变,只是页面(webview)整体往上滚了,且最大滚动高度(scrollTop)为软键盘高度。

⚪️ Android 软键盘弹起表现

同样,在 Android 上,输入框获取焦点,键盘弹起,但是页面(webview)高度会发生改变,一般来说,高度为可视区高度(原高度减去软键盘高度),除了因为页面内容被撑开可以产生滚动,webview本身不能滚动。

⚪️ IOS 软键盘收起表现

触发软键盘上的“收起”按钮键盘或者输入框以外的页面区域时,输入框失去焦点,软键盘收起。

⚪️ Android 软键盘收起表现

2、为什么fixed会失效

既然ios键盘弹起时,页面会上移,那么为什么fixed会失效呢。

这里参考这篇文章:ios键盘难题与可见视口(visualViewport)api

当时ios设计者考虑到一个问题:当键盘弹起时,页面无法感知到键盘的存在。那么,如果将要输入的目标(即「输入框」,例如 input、textarea 或一般的 contenteditable 元素)正好被弹起的键盘遮住,体验不会很糟糕吗?

为了解决这个问题,ios设计者们让webview上滚,但滚动的结果有些出乎意料:输入框本身可以理解地滚动到了实际可视区域的正中间,但 fixed 元素不会发生重新计算,而是保持原来的相对位置,跟着输入框一起被上推;

在滚动过程中,还会允许屏幕底部超出页面底部(「滚动过头」),以便让输入框尽可能露出来。收起键盘后,「滚动过头」的部分会被弹回,fixed 元素发生重新计算,但页面并不会回到与打开键盘前相同的位置。

3、怎么监听键盘弹起和收起的动作

既然是键盘弹起来造成的问题,那么解决这个问题必然需要监听键盘弹起和收起的动作,那怎么监听呢。同样参考这篇文章:WebView上软键盘的兼容方案 综合上面键盘弹起和收起在 IOS 和 Android 上的不同表现,我们可以分开进行如下处理来监听软键盘的弹起和收起:

⚪️ Ios

在 IOS 上,监听输入框的 focus 事件来获知软键盘弹起,监听输入框的 blur 事件获知软键盘收起。在 Android 上,监听 webview 高度会变化,高度变小获知软键盘弹起,否则软键盘收起。

// IOS 键盘弹起:当输入框被聚焦时IOS键盘会被弹起
inputRef?.current?.addEventListener('focus', function () {
  // IOS 键盘弹起后操作
}, false)

// IOS 键盘收起:当点击输入框以外区域或点击收起按钮,IOS输入框都会失去焦点,键盘会收起,
inputRef?.current?.addEventListener('blur', () => {
  // IOS 键盘收起后操作
})

⚪️ android

useEffect(() => {
    const { isAndroid } = Util.getOS('');
    let originHeight = document.documentElement.clientHeight || document.body.clientHeight;
    const handelAndroidResize = throttle(() => {
        const resizeHeight =
            document.documentElement.clientHeight || document.body.clientHeight;
        if (originHeight < resizeHeight) {
            // Android 键盘收起后操作
        } else {
            // Android 键盘弹起后操作
        }
        originHeight = resizeHeight;
    }, 300);

    if (isAndroid) {
        window.addEventListener('resize', handelAndroidResize, false);
    }

    return () => {
        if (isAndroid) {
            window.removeEventListener('resize', handelAndroidResize, false);
        }
    };
  }, []);

解决办法

下面就开始一一对上面说的问题进行分析解决:

1、吸顶元素能够继续吸顶

这个问题因为键盘弹出ios和安卓的处理方式不同,这个现象就只发生在ios系统中。

当时找了一圈方法,觉得并没有合适的解决方法,退而求其次,既然h5无没有办法很好的解决吸顶问题,那么这个能力不如就用客户端的能力好了,客户端的header不属于webview内容,自然webview上推时,客户端的header就还是吸顶状态。

我们当时的情况下,客户端的jsb能力只能够支持简单的一个返回按钮加一个居中标题作为header。所以有右上角的“历史评价”就不能够直接用jsb能力写,所以只能和ui同学商量,将原本的设计方案改一下。变成如下设计,就能够使用jsb能力写header了。

!https://cdn.nlark.com/yuque/0/2022/webp/260235/1649689642283-d10f5793-ea63-4925-8422-92b5b470f92a.webp#clientId=ue4253c9d-3936-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown error&from=paste&id=u7ab6589e&margin=[object Object]&originHeight=1043&originWidth=1304&originalType=url&ratio=1&rotation=0&showTitle=false&status=error&style=none&taskId=ud919d480-4e07-4346-bbfe-59f05b5b29a&title=

⚪️ 衍生问题:

但这样引出了一个新的问题:在安卓系统下的app端,会有底部按钮被遮挡的问题。

⚪️ GIF 如下:

⚪️ 衍生问题解决办法

之前header头用的是前端自己写的header时,没有这个问题,推测是因为安卓手机在键盘弹起时的webview高度缩短为整个屏幕的高度减去键盘的高度, 在之前的实现中,由于使用沉浸式header,所以前端webview高度就是整个屏幕的高度,而现在由于采用的是客户端jsb能力,所以webview剩余高度就需要减去header头的高度。所以解决办法就是让键盘弹起时,添加吸底按钮以及底部元素的margin-bottom为header的高度就行。

2、吸底元素(也就是按钮)能够在键盘弹出之后,出现在键盘的上方

对于这个问题,因为安卓表现是webview缩小,所以在安卓上并不存在这个问题,对于ios,因为ios向上滚动的距离最大是键盘的高度,但是也有可能滚动距离不是键盘高度,导致吸底按钮会被遮挡。

⚪️ 可见 GIF 如下:

!https://cdn.nlark.com/yuque/0/2022/webp/260235/1649689642795-941dd8e2-3f6c-47ca-8f35-27a387c31fa0.webp#clientId=ue4253c9d-3936-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown error&from=paste&id=u596b26d9&margin=[object Object]&originHeight=1334&originWidth=750&originalType=url&ratio=1&rotation=0&showTitle=false&status=error&style=none&taskId=u55ce3742-875c-4e83-b2d2-b0e8d3b1cdb&title=

⚪️ 解决办法:

让键盘弹起来的时候,让输入框加入scrollIntoView(true);方法。这其实可能只适用于我这种情景,这个解决办法的原理是:scrollIntoView(true)想让输入框的顶部滚动到与可视区顶部齐平的效果,但是由于ios键盘弹起之后最大滚动距离等于键盘的高度,所以,通过这个方法会让webview滚动距离等于ios键盘的高度,达到了吸底按钮吸底的效果。

3、键盘弹起,输入框出现在可视区内。(对于这点,ios本身是支持的,但是安卓却并不会主动让输入框出现在可视区域内)

这个简单,让元素滚动到可视区内,直接用scrollIntoView(true)方法就好。

4、ios软键盘收起时页面不能自然滑落

对于部分ios系统下的部分微信webview内,发现软键盘收起时,滚动上去的页面没有滚动下来,造成了下面区域留出了一片灰色的区域。其实这是 Apple 在 IOS 的 bug,会出现在所有的 Xcode10 打包的 IOS12 的设备上。微信官方已给出解决方案(点击查看)。

⚪️ 问题gif:

⚪️ 如图所示:

⚪️ 解决办法:

当键盘收起时,加入下面其中一种办法就可以解决

  1. 滚动到顶部 window.scrollTo(0,0)
  2. 滚动到底部 window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight)); 本场景代码 const handleIosInputBlur = () => { // IOS 键盘收起后操作 // 微信浏览器版本6.7.4+IOS12会出现键盘收起后,视图被顶上去了没有下来 const wechatInfoRe = /MicroMessenger\\/([\\d\\.]+)/i; const wechatInfo = wechatInfoRe.exec(window?.navigator?.userAgent); const wechatVersion = wechatInfo && wechatInfo.length > 1 && wechatInfo[1]; const osInfoRe = /OS (\\d+)_(\\d+)_?(\\d+)?/i; const osInfo = osInfoRe.exec(navigator.appVersion); const osVersion = osInfo && osInfo.length > 1 && osInfo[1]; if (!wechatVersion || !osVersion) { return; } const height = Math.max(document.body.clientHeight, document.documentElement.clientHeight); if (Number(wechatVersion.replace(/\\./g, '')) >= 674 && Number(osVersion) >= 12) { window.scrollTo(0, height); } };

最终效果:

Ios

!https://cdn.nlark.com/yuque/0/2022/webp/260235/1649689642941-4ee068b1-c68b-44f2-b547-092405b5a651.webp#clientId=ue4253c9d-3936-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown error&from=paste&id=u4f420a23&margin=[object Object]&originHeight=960&originWidth=442&originalType=url&ratio=1&rotation=0&showTitle=false&status=error&style=none&taskId=u145d5564-fad9-47bc-994e-453dc23dae6&title=

安卓

!https://cdn.nlark.com/yuque/0/2022/webp/260235/1649689643022-5ccc6322-539b-4130-9b49-5340dbeadeb2.webp#clientId=ue4253c9d-3936-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown error&from=paste&id=u71d35b41&margin=[object Object]&originHeight=1280&originWidth=576&originalType=url&ratio=1&rotation=0&showTitle=false&status=error&style=none&taskId=ua6acdb7b-e286-47a5-91de-014bc21460b&title=

参考文章:

WebView上软键盘的兼容方案

js如何获取iOS键盘高度

移动端input“输入框”常见问题及解决方法

ios键盘难题与可见视口(visualViewport)api

省流版

在移动端,当软键盘弹出时,可能会导致页面的 header(顶部导航栏)和 footer(底部导航栏)发生错位问题。这是因为软键盘的弹出会改变可视区域的高度,从而影响到页面布局。

下面是几种解决方案来解决移动端 input 框软键盘弹起导致 header 和 footer 错位问题:

固定定位

可以将 header 和 footer 设置为固定定位,以保持其位置不受软键盘弹出的影响。使用 CSS 的 position: fixed 属性可以将元素固定在屏幕的顶部或底部,不随页面滚动而移动。

header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
}

footer {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
}

通过固定定位,可以确保 header 和 footer 始终保持在页面的顶部和底部位置,不受软键盘弹出的影响。

软键盘监听事件

通过监听软键盘的打开和关闭事件,动态调整页面布局来避免错位问题。

在 JavaScript 中,可以监听 resize 事件来检测软键盘的打开和关闭。当软键盘打开时,可视区域的高度会发生变化,可以根据高度的变化来调整页面的布局。

// 监听软键盘弹出事件
window.addEventListener('resize', function() {
  // 获取窗口可视区域高度
  var viewportHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

  // 获取文档内容高度
  var documentHeight = Math.max(
    document.body.scrollHeight,
    document.documentElement.scrollHeight,
    document.body.offsetHeight,
    document.documentElement.offsetHeight,
    document.body.clientHeight,
    document.documentElement.clientHeight
  );

  // 计算可视区域高度和文档内容高度的差值
  var heightDifference = documentHeight - viewportHeight;

  // 判断软键盘是否弹出
  var isKeyboardOpen = heightDifference > 0;

  if (isKeyboardOpen) {
    // 软键盘弹出,调整页面布局
    // 例如,隐藏或移出可视区域的 header 和 footer
    document.getElementById('header').style.display = 'none';
    document.getElementById('footer').style.display = 'none';
  } else {
    // 软键盘关闭,恢复页面布局
    // 例如,显示 header 和 footer
    document.getElementById('header').style.display = 'block';
    document.getElementById('footer').style.display = 'block';
  }
});

在事件处理程序中,可以根据软键盘的打开和关闭状态,调整 header 和 footer 的位置或样式,以保持页面的正确布局。


或者直接监听 input 输入框的聚焦失焦事件来判断软键盘弹出:

// 获取输入框元素
var inputElement = document.getElementById('myInput');

// 监听输入框聚焦事件
inputElement.addEventListener('focus', function() {
  // 输入框获得焦点,隐藏 header 和 footer
  document.getElementById('header').style.display = 'none';
  document.getElementById('footer').style.display = 'none';
});

// 监听输入框失焦事件
inputElement.addEventListener('blur', function() {
  // 输入框失去焦点,显示 header 和 footer
  document.getElementById('header').style.display = 'block';
  document.getElementById('footer').style.display = 'block';
});

滚动处理

当软键盘弹出时,可以尝试自动将焦点定位到页面的其他区域,触发页面的滚动,从而将输入框移出软键盘的遮挡范围。

例如,可以在输入框获取焦点时,自动触发页面滚动,将输入框移至软键盘上方。可以使用 JavaScript 的 scrollIntoView 方法来实现滚动。

input.addEventListener('focus', function() {
  // 将输入框所在的区域滚动到可视区域
  this.scrollIntoView();
});

通过滚动页面,可以确保输入框位于软键盘之上,避免 header 和 footer 的错位问题。

最后更新于 2023-07-20