关于 h5 键盘兼容

@wenye  April 2, 2021

前言

相比较于 PC 端,移动端的输入方式是虚拟键盘,弹出式的键盘会造成一些在移动端上特有的兼容性的问题,而且只要是涉及到表单输入的时候都能遇到,也是比较常见的移动 h5 兼容的一种问题。也是最近经常做 h5 所以对这个问题进行了一次整理,感谢这篇文章让我收获颇丰: 可能这些是你想要的 H5 键盘兼容方案,本文借鉴了很多这篇文章上的点。

键盘在 Androidios 的表现

ios 上的表现

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

Android 上的表现

  • 输入框聚焦后弹起的键盘不会让键盘自动滚动可见的位置(亲测部分手机浏览器也会自动滚动可见,但是我们 h5 大部分都是在微信传播,这里只讨论微信浏览器的情况)
  • 键盘收起后输入框不会失焦
  • 除了点击键盘收起按钮能造成收起之外,点击网页的其他区域也可以
ios键盘
PS: 这里提供下上面 gif 中的测试网页地址,感兴趣的读者可以用自己的手机亲自尝试下: 测试 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

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

ios键盘

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

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

最后

其实写到这里也就应该结束了,但是键盘输入相关的问题肯定也不止这些,以后也许会发现更多奇奇怪怪的bug,这篇文章指不定还有续章💀


添加新评论