H5 移动端 键盘遮挡焦点元素解决方案

前言


最近在作 webapp,遇到了不少移动端兼容的问题,其中一个问题就是:输入框触发 focus 后,键盘弹出,而后遮住了输入框。css

而后在AndroidIOS上,这个问题的表现形式不同,而原生键盘和第三方键盘也不同,但引发的问题都是同样的:输入框被遮住了。html

须要的效果


在键盘弹出时,得到焦点的输入框要在可视区域内,效果以下图:android

键盘弹出、收起的表现


  1. IOS:web

    输入框获取焦点,键盘弹出,webview高度不会改变,但webview会往上滚,且最大滚动高度scrollTop为键盘高度。浏览器

    点击键盘上的收起按钮,或者输入框之外的页面区域时,输入框失去焦点,键盘收起。app

  2. Android:webapp

    输入框获取焦点,键盘弹出,可是webview高度会发生改变,高度为原高度减去软键盘高度。iphone

    点击输入框之外的区域时,输入框失去焦点,软键盘收起。而点击键盘上的收起按钮时,键盘收起 ,但输入框并不会失去焦点,测试

## 解决方案ui


当输入框位于页面下部位置时,在IOS中,webview会往上滚一段距离,使得获取焦点的输入框自动处于可视区,而在Android里,只会改变页面高度,而不会发生焦点元素滚动到可视区的事情。

因此IOS能够不用管,而Android须要在键盘弹出的时候,将输入框滚动到可视区。

获取设备类型


首先是要获取设备类型,经过navigator.userAgent获取便可。

function judgeDeviceType() {
  let deviceType = null;

  if (!deviceType) {
    const ua = window.navigator.userAgent.toLocaleLowerCase();
    const isIOS = /iphone|ipad|ipod/.test(ua);
    const isAndroid = /android/.test(ua);

    deviceType = {
      isIOS: isIOS,
      isAndroid: isAndroid
    };
  }

  return deviceType;
}

监听事件


IOS 能够经过focusblur事件监听键盘弹出、收起,但 Android 不行,但由于webview高度会变,因此经过监听resize事件解决。

export function listenAndroidKeybord() {
  const { isAndroid } = judgeDeviceType();

  if (isAndroid) {
    const androidResize = function() {
      // 将当前焦点元素滚动到可视区
      activeElementScrollIntoView();
    };

    // android 键盘弹出、收起,可视区高度会发生变化
    window.addEventListener('resize', androidResize, false);

    return () => {
      window.removeEventListener('resize', androidResize, false);
    };
  }
}

将元素滚动到可视区


要将元素滚动到可视区,主要有两个方法:scrollIntoViewscrollIntoViewIfNeeded,兼容性在移动端都很不错。

function activeElementScrollIntoView() {
  const activeEl = document.activeElement;

  if (
    activeEl.tagName === 'INPUT' || 
    activeEl.tagName === 'TEXTAREA'
  ) {
    window.setTimeout(() => {
      if ('scrollIntoView' in activeEl) {
        activeElt.scrollIntoView();
      } else {
        activeEl.scrollIntoViewIfNeeded();
      }
    }, 100);
  }
}

MiuiBrowser


以上代码能够说解决了大部分浏览器键盘遮挡问题了,但我用本身的小米手机自带的小米浏览器测试时,出了问题,键盘弹出,页面纹丝不动,手动去拖,有时行,有时不行。

搞了好久,发现了两个问题,我这手机上自带的小米浏览器,userAgent 上没有带Android标识,但有MiuiBrowser标识,。而后,页面有时能拖动,有时不能拖动,我猜应该是webview的可视区高度变化有问题,或者是个人代码监听resize致使有问题。

解决方案

  1. 增长设备类型判断

    const ua = window.navigator.userAgent.toLocaleLowerCase();
    const isMiuiBrowser = /miuibrowser/.test(ua);
  2. 经过监听focusblur事件来监听键盘弹出、收起,而后给body加高度

    body, html {
      height: 100%;
    }
    function listenMiuiBrowserKeybord() {
      const { isMiuiBrowser } = judgeDeviceType();
    
      if (isMiuiBrowser) {
        const inputFocus = function() {
          document.body.style.marginBottom = '50px';
          activeElementScrollIntoView();
        };
    
        const inputBlur = function() {
          document.body.style.marginBottom = '0px';
          activeElementScrollIntoView();
        };
    
        let $inputs = document.getElementsByTagName('input');
        for (let i = 0; i < $inputs.length; i++) {
          $inputs[i].addEventListener('focus', inputFocus, false);
          $inputs[i].addEventListener('blur', inputBlur, false);
        }
    
        return () => {
          for (let i = 0; i < $inputs.length; i++) {
            $inputs[i].removeEventListener('focus', inputFocus, false);
            $inputs[i].removeEventListener('blur', inputBlur, false);
          }
        };
      }
    }

坑点:这种方案虽然解决了弹出问题,但点击键盘收起按钮,Android 下输入框并不会失去焦点,须要失去焦点才能让 body 增长的高度变为 0。

备注


解决方案并不完善,踩坑路漫漫。