头图
本文参与了1024 程序员节活动,欢迎正在阅读的你也加入。

经过多番查阅资料,发现只通过前端是无法完美处理该问题的。主要原因在于:

  1. js没有相关接口可以准确获取安卓手机软键盘的高度,这样也就无法处理当软键盘弹出时,能够将输入框顶上去,并且与软键盘完美的贴合,比如输入框在最底部的场景,此时页面滚动高度不够,因此无法准确的滚动到底,使得软键盘将输入框顶上去并与之完美的贴合。
  2. js并不能监听软键盘关闭的事件,比如点击折叠按钮关闭软键盘,此时输入框焦点还在,无法触发输入框的blur事件,有人说会触发resize事件,但是经过我的测试,resize事件并未触发。

以下是我的解决方案。首先封装一个获取页面视图宽高的工具函数,代码如下:

 const getViewSize = ():{ width:number;height:number } {
    if (window.innerWidth) {
      return {
        width: window.innerWidth,
        height: window.innerHeight
      };
    } else if (document.compatMode === 'CSS1Compat') {
      return {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      };
    } else {
      return {
        width: document.body.clientWidth,
        height: document.body.clientHeight
      };
    }
 }

其次,监听页面resize事件,同时也监听输入框的focus和blur事件。如下:

input.addEventListener('focus',this.onSetScrollHandler.bind(this,true));
input.addEventListener('blur',this.onSetScrollHandler.bind(this,false));
window.addEventListener('resize',this.onSetScrollHandler.bind(this,false));

接下来是onSetScrollHandler函数的处理,首先我们要考虑2种情况,第一那就是如果页面滚动高度足够,滚动到输入框时,页面的剩余滚动高度刚好可以大于等于软键盘的高度,此时就可以做到完美贴合输入框。那如果滚动高度不够,我们是需要将页面根元素高度给增大的,而我们是通过设置style的height来将根元素高度增大的。因此在触发blur事件或者是触发resize事件,高度变动时,就需要将根元素的高度恢复原样,因此我们需要先缓存页面的高度,以及是否存在高度的设置。如下:

const originHeight = getViewSize().height;
const originBodyHeight = document.body.style.height;

我们可以看到onSetScrollHandler是添加了一个布尔值参数的,用来做判断的,当然此时因为会触发resize事件,我们还需要单独计算状态。

此外由于这个问题是安卓手机出的,为了避免我们加的代码影响到ios手机又或者其它设备默认是实现的软键盘弹起顶上去的功能,我们需要添加环境的判断。如下:

type envReturnType = {
    isBrowser: boolean;
    isServer: boolean;
    isMobile: boolean;
    isAndriod: boolean;
    isIos: boolean;
    canUseWorkers: boolean;
    canUseEventListeners: boolean;
    canUseViewport: boolean;
}
const getEnv = (): envReturnType => {
    const inBrowser = Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);
    const isMobileVailable = (reg: string | RegExp): boolean => Boolean(navigator.userAgent.match(reg));
    const getEnvObject = [
        isBrowser: inBrowser
        isMobile: isMobileVailable(/(iPhoneliPod]Androidlios)/i)Test Regex...
        isAndriod: isMobileVailable(/(android)/i),
        isIos: isMobileVailable(/(iPhoneliPodlios)/i),
        isServer: !inBrowser,
        canUseWorkers: typeof Worker !==  undefined!
        canUseEventListeners: inBrowser && Boolean(window.addEventListener)
        canUseViewport: inBrowser && Boolean(window.screen)
    ];
    return Object.assign(0bject.values(getEnvObject),getEnvObject);
}

因此在onSetScrollHandler函数内部首先要做的就是判断是否是安卓手机。如下:

const onSetScrollHandler = (status: boolean) => {
    const { isAndriod } = getEnv(); 
    if(!isAndriod){
        return;
    }
    // 后续代码
}

接着我们获取body元素的scrollTop,然后获取到需要被顶上去的输入框的scrollTop加上它的高度就是我们要滚动的距离,然后为了保证兼容性,我们需要设置document.body.scrollTop和document.documentElement.scrollTop,当然在部分安卓手机上,可能这2个设置都不会生效,这时候需要调用元素的scrollIntoView方法。同时我们还需要修改body元素的高度,由于无法获取到软键盘的准确高度,这时候我们需要修改body元素高度为2个屏幕高度,这样才能达到让剩余滚动高度足够大于软键盘高度。最终版本代码如下:

const onSetScrollHandler = (status: boolean) => {
    const { isAndriod } = getEnv(); 
    if(!isAndriod){
        return;
    }
    const { height: resizeHeight } = getViewSize().height;
    // 这里主要是为了还原
    const { scrollTop } = document.body || document.documentElement;
    if(status || resizeHeight < originHeight){
        // 撑大根元素高度,方便滚动
        document.body.style.height = `${originHeight + resizeHeight}px`;
        const top = input.offsetHeight + input.scrollTop;
        document.body.scrollTop = document.documentElement.scrollTop = top;
        // 确保兼容性,还得调用scrollIntoView方法还原
           input.scrollIntoView();
    }else {
         // 如果不存在原始高度,则移除height属性,否则重新设置height
        if(!originBodyHeight){
            document.body.style.removeProperty('height');
        }else{
            document.body.style.height = `${originBodyHeight}px`;
        }
        // 还原scrollTop
        document.body.scrollTop = document.documentElement.scrollTop = scrollTop;
       // 确保兼容性,还得调用scrollIntoView方法还原
       document.body.scrollIntoView();
    }
}

一个小小的软键盘遮挡问题,竟然要写出这么多的兼容性代码,兼容真的好蛋疼。

以上方案还并不算完美的处理掉了这个问题,正如开头提到的2个问题限制,因此当出现开头提到的场景,以上的代码就不能解决,也可以算作是体验问题。最完美的方案还得是端上做配合才行。


夕水
5.2k 声望5.7k 粉丝

问之以是非而观其志,穷之以辞辩而观其变,资之以计谋而观其识,告知以祸难而观其勇,醉之以酒而观其性,临之以利而观其廉,期之以事而观其信。