H5页面键盘问题收集--一叶障目

1,047 阅读2分钟

引言

       移动端相较与PC页面,其中移动端兼容就是一个比较让人头疼的事情,今天就将移动端键盘相关进行收集汇总,以助各位同仁一臂之力。移动端的输入方式是虚拟键盘,弹出式的键盘会造成一些在移动端上特有的兼容性的问题,而且只要是涉及到表单输入的时候都能遇到,也是比较常见的移动 h5 兼容的一种问题。

ios端微信浏览器输入法(软键盘)问题

         首先需要知道ios对软键盘弹出时的处理是页面整体往上滚,且最大滚动高度为软键盘的高度。

存在的问题:

ios微信版本v6.7.4及以上,一直存在的问题是输入框获取焦点,键盘弹起,页面整体往上滚动,当键盘收起后,不会回到原位,导致键盘原来所在位置是空白的。具体效果如下:

正常页面:

弹出:

收起:

解决方案:

监听软件盘的弹起和收起(留白问题主要是要监听收起),然后手动地对页面进行滚动,以消除软键盘对页面的影响。

使用scrollIntoView()或者window.scrollTo(x-coord, y-coord)来实现。

以用户名和手机号为例,input组件提供了focus和blur事件,可以以此为依据来判断键盘的弹起或者收起(当inputItem focus时,软键盘会弹起;当inputItem blur时,软键盘会收起)。

template>
...
<div class="apply-form">
  ...
  <div>
    ...
    <input
      ...
      title="用户名"
      @blur="nameBlured"
    ></input>
    <input
      ...
      title="手机号码"
      @blur="phoneNumberBlured"
    ></input>
    ...
  </div>
</div>
...
</template>
<script>
...
export default {
  methods: {
    nameBlured() {
      // 用户名失焦时的其他逻辑
      ...
      //

      this.$el.querySelector('.apply-form').scrollIntoView() // 手动滚动页面,消除软键盘的影响
    },
    phoneNumberBlured(value) {
      // 手机号失焦时的其他逻辑
      ...
      //

      this.$el.querySelector('.apply-form').scrollIntoView() // 手动滚动页面,消除软键盘的影响
    },
  }
}
</script>

使用window.scrollTo(x-coord, y-coord)也可以实现效果,具体的滚动位置可以根据实际情况确定。

android软键盘遮挡问题

存在的问题:

与ios不同的是,android默认软键盘弹出时页面是不会被向上推起的,这样会导致软键盘遮挡住输入框,带来不好的用户体验。

如下图所示,手机号focus的时候,软键盘遮挡住了该输入框。

解决方案:

解决的思路是在对应的输入框聚焦的时候,手动的使输入框进入到可视区域,实现的方式是使用scrollIntoView来解决

template>
...
<div class="apply-form">
  ...
  <div>
    ...
    <input
      ...
      title="用户名"
       @focus="inputFocused"
    ></input>
    <input
      ...
      title="手机号码"
       @focus="inputFocused"
    ></input>
    ...
  </div>
</div>
...
</template>
<script>
...
export default {
  methods: {
    inputFocused() {
      if (ddbridge.app.system === 'android') { // 判断设备类型
        // scrollIntoView()会把指定的元素向距离可视区域的最近方向进行滚动
        // 为避免将输入框直接滚动到页面最上方,这里是对输入框的父元素进行了滚动
        // 从而使输入框相对而言处于页面中间位置
        this.$el.querySelector('.apply-form').scrollIntoView()
      }
    }  }
}
</script>

此外,也可以在输入框聚焦的时候使用window.scrollTo(x-coord, y-coord)方法来手动进行页面滚动,具体的滚动位置可以根据实际情况确定。

键盘在 Android 和 ios 的表现

在 ios 上的表现

  • 输入框聚焦后弹出键盘,会自动滚动到让输入框可见,键盘收起后滚动条不会回归
  • 点击键盘完成才会收起键盘并且输入框会失焦,点击网页其他区域不会取消掉输入框焦点

在 Android 上的表现

  • 输入框聚焦后弹起的键盘不会让键盘自动滚动可见的位置(亲测部分手机浏览器也会自动滚动可见,但是我们 h5 大部分都是在微信传播,这里只讨论微信浏览器的情况)
  • 键盘收起后输入框不会失焦
  • 除了点击键盘收起按钮能造成收起之外,点击网页的其他区域也可以

通过以上的对比可知,ios 对于键盘的弹起和收起的用户体验是比较好的。在安卓中不会自动将输入框滚动到可见区域就给人一股很劣质的网页感觉,而且收起键盘后输入框不会自动失焦也造成了无法通过监听输入框的 blur 事件来判断键盘是否收起了,所以这也就引出了下一个问题,如何监听键盘的弹出和收起?

监听键盘的弹出收起

由于在 ios 上,输入框聚焦则键盘弹出,收起则输入框自动失去焦点,因此我们很容易知道可以通过输入框自身的focus或者blur事件来判断键盘的弹起状态。

/** 监听ios的键盘弹起和收起: 通过focus和blur事件 */
const inputs = document.querySelectorAll(
    "input,textarea,*[contenteditable=''],*[contenteditable='true']"
);
inputs.forEach((input) => {
    input.addEventListener("focus", () => {
        console.log("ios键盘弹出");
    });
    input.addEventListener("blur", () => {
        console.log("ios键盘收起");
    });
});

       不过遗憾的是,Andriod上键盘的收起并不会导致输入框失去焦点,也就是无法通过blur事件去监听键盘的收起,那我们要如何判断呢?

这里我们就要提到Andriod上键盘弹出收起所会造成的另一种表现了,那就是会造成webview的高度变化,我们可以通过监听高度的变化来判断键盘的状态。

/** 获取可视高度 */
function getClientHeight() {
    return document.documentElement.clientHeight || document.body.clientHeight;
}

let origin = getClientHeight();
window.addEventListener("resize", () => {
    const resize = getClientHeight();
    if (origin > resize) {
        console.log("andriod键盘弹出");
    } else {
        console.log("andriod键盘收起");
    }
    origin = resize;
});

但是监听键盘弹出和收起并不是我们的目标,优化键盘的体验才是。明显ios上的体验更佳,为此我们将andriod上的行为修改成和ios一致。

let origin = getClientHeight();
window.addEventListener("resize", () => {
    const resize = getClientHeight();
    if (origin > resize) {
        console.log("andriod键盘弹出");
        // 和ios保持一致: 自动滚动到聚焦的输入框
        // const focusEl = document.querySelector(
        //   "input:focus,textarea:focus,*[contenteditable='']:focus,*[contenteditable='true']:focus"
        // );
        const focusEl = document.activeElement; // 有兼容性问题,老的浏览器可能不支持
        if (focusEl)
            focusEl.scrollIntoView({
                behavior: "smooth",
                block: "center"
            });
    } else {
        console.log("andriod键盘收起");
        // 和ios保持一致: 键盘收起之后去掉聚焦
        // const focusEl = document.querySelector(
        //   "input:focus,textarea:focus,*[contenteditable='']:focus,*[contenteditable='true']:focus"
        // );
        const focusEl = document.activeElement;
        if (focusEl) focusEl.blur();
    }
    origin = resize;
});

     这里有个需要注意的地方,在获取焦点元素的方法中我注释掉了通过querySelector的方式而采用了document.activeElement的写法,是因为这种写法是有一些兼容性问题的,如果你的网页跑在偏低版本的浏览器中可能会不支持。

一个ios滚动后的空白

       这个bug也有不少同学遇到过,那就是在ios12的微信上,如果输入框比较靠近底部,在弹出键盘又收起后,因为滚动而被顶起的页面不会自己回滚到底部位置,导致原本弹起键盘的位置是空的。

       解决方法也是很简单,就是在键盘弹出时候记录滚动条的位置,在键盘收起时候重置滚动条即可。

/** 获取微信信息 */
function getWechatInfo() {
    const ua = window.navigator.userAgent.toLocaleLowerCase();
    const match = ua.match(/MicroMessenger\/((\d+)?\.(\d+)?\.(\d+)?)/i);
    return {
        match: match !== null,
        version: (match && match[1]) || "",
    };
}
/** 获取页面滚动条高度 */
function getPageScrollTop() {
    // 这里为什么这么写可以参考这篇文章: https://www.jianshu.com/p/fb867e8109f7
    return document.documentElement.scrollTop || document.body.scrollTop;
}
/** 设置页面滚动条高度 */
function setPageScrollTop(height) {
    document.documentElement.scrollTop = height;
    document.body.scrollTop = height;
}

const wechatInfo = getWechatInfo();
const inputs = document.querySelectorAll(
    "input,textarea,*[contenteditable=''],*[contenteditable='true']"
);
let scrollTop = 0;
inputs.forEach((input) => {
    input.addEventListener("focus", () => {
        console.log("ios键盘弹出");
        scrollTop = getPageScrollTop();
    });
    input.addEventListener("blur", () => {
        console.log("ios键盘收起");
        // IOS12和微信6.7.4以上版本键盘收起后,重置滚动条位置
        if (wechatInfo.match !== true) return;
        if (
            Number(wechatInfo.version.replace(/\./g, "")) >= 674 &&
            Number(osInfo.version) === 12
        ) {
            setPageScrollTop(scrollTop);
        }
    });
});

         这个bug也是造成我之前遇到的另一个怪异现象的问题所在,就是在弹窗中,为了防止滚动穿透往往会采用将body fixed掉的方式来禁用页面滚动条。如果这个时候弹窗有输入框,键盘的弹出会造成滚动条整体上移,但是这个时候滚动条实际上已经是禁用的,但是在无形中也许是渲染机制问题,实际上页面整体还是上移了但是碍于滚动条禁用而看不出效果,这时候诡异的一幕就出现了 ,你再也无法通过点击输入框来使得输入框聚焦,而是要点击输入框外面之上的某个地方才会让它聚焦,就像是响应点击的整体渲染布局都整体往上移动了,造成点击不灵的情况,很是诡异。不过好在这个问题只出现在ios12上,影响有限

兼容 Android 小米浏览器的 Hack 方案

        在 Android 的小米浏览器上,应用上面的方案,发现聊天输入框还是被遮挡得严严实实,scrollIntoView() 仍然纹丝不动。所以猜测,其实是滚到底了,软键盘弹起,页面实现高度大于可视区高度,这样只能在软键盘弹起后,强行增加页面高度,使输入框可以显示出来。综合上面兼容第三方输入法

   // Andriod 键盘收起:Andriod 键盘弹起或收起页面高度会发生变化,以此为依据获知键盘收起
   if (judgeDeviceType.isAndroid) {
       var originHeight = document.documentElement.clientHeight || document.body.clientHeight;

       window.addEventListener('resize', function () {
           var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
           if (originHeight < resizeHeight) {
               console.log('Android 键盘收起啦!');
               // Android 键盘收起后操作
               // 修复小米浏览器下,输入框依旧被输入法遮挡问题
               if (judgeDeviceType.isMiuiBrowser) {
                   document.body.style.marginBottom = '0px';
               }
           } else {
               console.log('Android 键盘弹起啦!');
               // Android 键盘弹起后操作
               // 修复小米浏览器下,输入框依旧被输入法遮挡问题
               if (judgeDeviceType.isMiuiBrowser) {
                   document.body.style.marginBottom = '40px';
               }
               activeElementScrollIntoView($input, 1000);
           }

           originHeight = resizeHeight;
       }, false)
   }

总结

H5 端前路漫漫,坑很多,需要不断尝试。