固定在 H5 底部的输入框的兼容性问题

2,107 阅读9分钟

缘起

  今年三四月的时候,有个系统要重构,看到设计稿上有个固定在底部的输入框,第一直觉就是事情不简单,一定会遇到各种兼容性问题,毕竟之前也看过一些处理类似问题的文章。不过还是想着要不试试看,都 2022 年了,手机页面又不考虑 IE,还能有啥兼容性问题呢?

11.png

辛酸泪

  粗略搜了一下,兼容性问题主要是键盘会挡住输入框。在做技术方案的时候,就曾搞了个 demo 给产品体验,没有任何特殊处理,看她录屏是没有挡住的(但是键盘出现的时候点不了输入框旁边的表情按钮)。虽然如此,心中难免忐忑。

  查了很多资料,大家对于防挡的处理无外乎两种:使元素scrollIntoView和把页面滚动到底部。最后定了如下写法:

const onFocusInput = () => {
    // 当输入框聚焦的时候,先隐藏表情列表(有可能刚才在输入表情,然后切回键盘)
    emit('setIsShowEmotionList', false);
    // 消息列表覆盖蒙层
    emit('setIsShowOverlay', true);
    setTimeout(() => {
        // 隐藏底部安全边距,即控制下面这个样式是否生效
        // padding-bottom: constant(safe-area-inset-bottom);
        // padding-bottom: env(safe-area-inset-bottom);
        isShowSafetyPadding.value = false;
    });
    if (isInIOS) {
        // 解决 ios 键盘弹出后,键盘遮挡输入框的问题
        setTimeout(() => {
            textareaObj.dom.scrollIntoView(false);
            setTimeout(() => {
                document.documentElement.scrollTop = document.documentElement.scrollHeight;
            }, 0);
        }, 0);
    }
};

  那个键盘出现时不能点按钮的问题也很好解决,输入框失焦时加个 0 ms 的定时器把安全边距展示出来即可。由于测试设备十分有限,这段代码勉强通过了测试。

  之所以加个 iOS 判断,是因为在有限的测试设备里,只发现部分 iPhone 有问题。聚焦输入框时,Android 手机会把页面高度减小,类似于浏览器窗口化一样。但是 iOS 是把页面整体向上推,有时候它又不往上推,就出现了 bug。

  后面开发完成,产品体验时提出 Safari 里键盘与输入框有间距,没有紧密贴合,翻回之前的聊天记录,当初确实忽略了。战战兢兢2个月,噩梦终于开始了……

  所以总结起来,放在页面底部的输入框,可能存在三个问题:

  • 键盘挡住输入框
  • 键盘出现后,点击输入框旁边的按钮没有反应
  • Safari 里键盘与输入框有间距,没有紧密贴合

  在测试时,前两个问题都算通过了,不过以防万一,还是在公司前端群把间距问题连同挡输入框的问题一起问了,群里问的时候只得到一个 VisualViewport 的结果(参考《iOS 键盘难题与可见视口(VisualViewport)API》),但是并没有解决空白问题,又看到 MDN 里说是实验性功能,就没有采用。

  过了几个月,还是接到一个反馈说 iPhone 6 会被挡住,借了测试机过来试,确实必现。于是我又迷茫起来……

  最近另一个系统需要优化,又不可避免要处理这个问题,于是参照上面的改了,不过把 scrollIntoView 的参数改成了 true,试了下测试机 iPhone 6,正常了。这个参数的含义是该元素要与视口顶对齐还是底对齐,之前我认为输入框要贴住键盘,自然是底对齐(false),并且在有限的测试设备上能正常显示,就没有试过顶对齐。它还有对象形式的参数,可以提供更丰富的功能,但是MDN显示 iOS Safari 不支持,就没有用。

  但是,还是那句话,测试设备毕竟有限,不能保证放之四海而皆准。

业界方案

  前文提到,我查了很多资料,大家的处理无外乎两种:使元素scrollIntoView和把页面滚动到底部。但是往往都有一些判断,局限在部分机型、系统版本或浏览器内,而这些判断大家的文章里是不一致的,因此难以照抄。   

  也看了下其他 APP 的 H5 里固定在底部的输入框,大部分都因为证书问题无法抓包,少数即使可以抓到,看打包后的代码也很难理清逻辑。有些甚至可能会处理键盘收起时输入框定位的问题之类的,不过还好,目前没有接到类似反馈,乐观点,就当做没有吧。

  不过优酷的在线客服,可以直接在浏览器打开,审查可以看到是 flex 布局。而且代码还比较清晰,可以看到是在聚焦后 0.3 s 和 1 s 的时候都执行了一个函数:

  注意到有个r = UB()会直接决定scrollIntoView(false)执不执行,UB 函数的实现是这样的:

  看到这段代码(主要是那个让我怀疑已经放弃部分用户的正则判断),想到了这篇标题特别吸引我的文章《彻底解决H5中键盘遮挡输入框的问题》,里面有相同的代码:

  文章末尾还给出了下面这段代码的页面链接

  跟上面优酷的代码不能说十分相似,只能说是一模一样了。但是看文章下面评论,还是有人说不灵的,不免怀疑题文不符……

  然后我也试了用 iPhone 6 的 Safari 打开,默认情况下输入框都被挡住了,可能他们真的已经放弃了 iPhone 6 吧(更具体地说是低版本的 iPhone 6,毕竟没有试过更高版本的。另外注意到优酷客户端要求 iOS11 以上,测试机是 10.2,检查更新提示可以更新到12.5.6)。不过滑动一下就可以了,并且键盘出现时也是正常的。

  另外补充一下,优酷客服页面上,输入框失焦后,也会延时 0ms 补充底部安全边距的,代码略为分散,就不截图了。

结论

  我搞了一个简单的测试网页,又列了一个飞书表格,列举了几种情况在几种设备下的表现,一开始没有考虑齐全,部分情况都是后续补充的,因此测试数据有缺失,也欢迎大家补充完善。其中并没有列出优酷这种方式,如果大家有兴趣可以直接访问优酷的在线客服页面

image.png

  基于有限的测试结果,暂时有如下结论:

  1. 为避免挡住,可以在聚焦输入框时,执行:
inputFocusHandle() {
    setTimeout(() => {
        // 隐藏底部安全边距,即控制下面这个样式是否生效
        // padding-bottom: constant(safe-area-inset-bottom);
        // padding-bottom: env(safe-area-inset-bottom);
        this.isShowSafetyPadding = false;
    });
    // 处理非 Safari 的情况(主要指公司 APP,其他未测)
    if (isIOS() && !isSafari()) {
        // 解决 ios 键盘弹出后,键盘遮挡输入框的问题
        setTimeout(() => {
            this.$refs.textArea.scrollIntoView(true);
            setTimeout(() => {
                document.documentElement.scrollTop = document.documentElement.scrollHeight;
            }, 0);
        }, 0);
    }
}

输入框失焦时,执行:

inputBlurHandle() {
    setTimeout(() => {
        // 显示安全下边距
        this.isShowSafetyPadding = true;
    });
}
  1. Safari 里输入框与键盘之间的间距是无法去除的,但是默认情况下不会显示出来(也可能展示一部分,不会太多)。

  2. 对于以上处理,无论 fixed 布局还是 flex 布局均可,但是 fixed 布局会更麻烦一点,因为可能要动态设置正文下边距以防遮挡。

  不管怎么说,由于测试设备十分有限,下任何结论都难免心虚。就像上面那篇阿里小蜜的文章,他们必然也验证了大量机型,但是依然有些评论反馈说方法无效的(受限于测试设备,评论也未给出更多信息,难以验证真假)。

荒唐言

  前文多次提到“测试设备有限”,其实现在虽然设备有限,也没有每个APP都测一遍。更何况,无限又如何呢,难道真要每部手机都去试一遍吗?将来出了新机型,又要回到这来加个if吗?

  如果我们可以说“不支持 VisualViewport API 的设备或浏览器,我们可以不用支持”,那就直接用 VisualViewport API 就好了。

  如果我们可以说“优酷(或阿里小蜜,或再拔高一点,比如整个阿里系APP?)不支持的设备或浏览器,我们也不用支持”,那就照抄知乎上那篇阿里小蜜的文章的方法就好了。

  如果能有个办法,就类似 JS 的 polyfill 一样,能覆盖浏览器本身的行为就好了,不过可能那个办法所需的代码需要其他编程语言来写——假如真的可以,想必也有开源方案。

  …………

  有人说“Safari 是新时代的 IE”,在键盘挡住底部输入框这个问题上,初期我也确实特别困惑,为什么出现键盘时 Safari 浏览器视口不缩小,而是向上平移呢(虽然有些手机可以发现 innerHeight 变化,但是固定在顶部的元素依然不可见),后面测试得多了,觉得这种处理也并非一无是处。如果是 Android 那种处理方式(缩小视口高度),可能页面上有其他固定定位的元素把仅剩的视口占满了,也有可能挡住输入框的。

  之前也不知道在哪看到一篇文章说 iOS 的设计者不希望手机上有固定定位的元素,因为手机屏幕已经够小了。好像是在解释为什么有些 iOS 版本不支持 fixed 定位。设计者的这种想法我也不知道对不对,但是用原生技术限制前端实现,好像有点霸道。

  前端技术号称“跨平台”,终究受限于浏览器。近些年大火的小程序,也有很多兼容性问题,不只是跨设备,还有跨宿主 APP 的问题。即使没有了 IE,前端同行们还有很多适配工作要做。

  突然又想到 Firefox,之前看新闻说 Chrome 给他们捐赠了很多钱,新闻提到他们的市场份额已经很低了,又想到有些人希望出个国产内核的浏览器之类的,感觉这件事情多少有点矛盾:一边渴望统一,一边渴望反垄断。如果统一了,同样的事情为什么要大家每个人都去做一遍呢?如何保证创造力不会被抹杀呢?如果只有少数人做,又怎么防垄断呢?

  越想越多,思绪纷乱,不知所言……