Android输入框聚焦时滚动不正确,如果没有body元素

21

移动浏览器弹出键盘时,会尝试滚动条以便让输入框仍然在视图中。

iOS Safari似乎通过找到最近的滚动父级来正确执行此操作。

在Android本地或Chrome移动浏览器中,它似乎只尝试使用body元素,然后放弃,因此聚焦的输入框被键盘隐藏。

如何打破它

在body元素上设置overflow-y: hidden。创建一个可滚动的容器并将表单放入其中。

当您选择屏幕底部附近的元素时,它将被键盘遮挡。

演示

http://dominictobias.com/android-scroll-bug/

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
    <title>Android scroll/focus bug</title>
    <style>
    html, body {
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
    }
    .scroll {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        overflow-y: scroll;
    }
    input {
        margin-bottom: 20px;
        width: 100%;
    }
    </style>
</head>
<body>

    <div class="scroll">
        <input type="text" value="Input 1">
        <input type="text" value="Input 2">
        <input type="text" value="Input 3">
        <input type="text" value="Input 4">
        <input type="text" value="Input 5">
        <input type="text" value="Input 6">
        <input type="text" value="Input 7">
        <input type="text" value="Input 8">
        <input type="text" value="Input 9">
        <input type="text" value="Input 10">
        <input type="text" value="Input 11">
        <input type="text" value="Input 12">
        <input type="text" value="Input 13">
        <input type="text" value="Input 14">
        <input type="text" value="Input 15">
        <input type="text" value="Input 16">
        <input type="text" value="Input 17">
        <input type="text" value="Input 18">
        <input type="text" value="Input 19">
        <input type="text" value="Input 20">
    </div>

</body>
</html>

有什么想法可以修复这个问题吗?需要进行一些浏览器检测和混乱的hack吗?


1
FYI <div class="scroll"> - Matt Derrick
4个回答

51

这是 Android 原生浏览器中的一个 BUG。顺便说一下,在软键盘上输入一个字符后,输入框会滚动到视图中。

在页面的某个位置放置以下代码片段应该有所帮助:

if(/Android 4\.[0-3]/.test(navigator.appVersion)){
   window.addEventListener("resize", function(){
      if(document.activeElement.tagName=="INPUT"){
         window.setTimeout(function(){
            document.activeElement.scrollIntoViewIfNeeded();
         },0);
      }
   })
}

3
请注意,Android 4.4和5也需要这个,不仅仅是"4.[0-3]"。 - pauloya
在我正在处理的页面上,输入框大约有95%的时间会正确滚动到可见位置,但偶尔也会出现不滚动的情况(在Android 5.1上的Chrome版本43)。scrollIntoViewIfNeeded似乎可以强制进行所需的滚动。那么这个bug在Android上的Chrome浏览器中也可能存在吗?原始问题还提到了原生的Android浏览器和Chrome。 - Coleman
@Serge MDN建议不要在生产环境中使用此功能 > https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoViewIfNeeded - Rahul Gupta
2
你应该使用document.activeElement.scrollIntoView(),因为其他浏览器也更好地支持它。 - Rahul Gupta
我们发现在某些安卓设备上,当一个输入框被聚焦时,通过触摸聚焦到另一个输入框会导致 WebView 向上滚动。这个解决方案也解决了这个问题。 - Steven Mark Ford
你们中有人知道 Bugzilla 上的哪个 bug(如果是 WebKit 的 bug)或 Slick / Chrome 使用的任何其他 bug 跟踪工具上的 bug 是什么吗? - UndefinedBehavior

9

Serge的回答很好,但是我有一些修改可以让它更适合我。

这个问题在我的Android 6上也出现了,所以我进行了一些检查,并且需要这个修复方法能够适用于文本区域和输入框。

if(/Android [4-6]/.test(navigator.appVersion)) {
   window.addEventListener("resize", function() {
      if(document.activeElement.tagName=="INPUT" || document.activeElement.tagName=="TEXTAREA") {
         window.setTimeout(function() {
            document.activeElement.scrollIntoViewIfNeeded();
         },0);
      }
   })
}

如果有人需要在 Angular 1 中进行修复,这是我使用的方法。

angular.module('<module name>').run(function ($window, $timeout) {
    if(/Android [4-6]/.test($window.navigator.appVersion)){
        $window.addEventListener("resize", function(){
            if(document.activeElement.tagName=="INPUT" || document.activeElement.tagName=="TEXTAREA"){
                $timeout(function() {
                    document.activeElement.scrollIntoViewIfNeeded();
                });
            }
        });
    }
});

你能解释一下这是如何工作的吗?具体来说,为什么我们要将它添加到“resize”事件中,因为对于移动屏幕来说,它永远不会被调整大小,对吧? - Rahul Gupta
1
@RahulGupta 在 Android 上打开键盘会触发 resize 事件。我不认为在 iOS 上打开键盘会触发 resize,但我们只需要在这里修复 Android。 - Zack Huston
"Select" 标签无法正常工作。我尝试使用以下代码:if(document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA' || document.activeElement.tagName == 'SELECT') { document.activeElement.scrollIntoView(); } - Jyoti Duhan

6

如果能节省时间,我可以稍作修改:

提供一些小的修订。
  • No need to specify an Android version # (less likely to break when your user gets Android 7.0+)
  • No need to wrap in a setTimeOut
  • MDN advises against .scrollIntoViewIfNeeded bc of browser incompatibility => .scrollIntoView is a workable substitute with slightly more browser compatibility

    if(/Android/.test(navigator.appVersion)) {
       window.addEventListener("resize", function() {
         if(document.activeElement.tagName=="INPUT" || document.activeElement.tagName=="TEXTAREA") {
           document.activeElement.scrollIntoView();
         }
      })
    } 
    

1
"setTimeout" 真的很需要,我已经测试过了,这样做更加稳定! - James Yang
@Derek Mueller - 2020年 - 上述的JS代码片段(不含jQuery)仍在Android手机的Chrome 78.x上成功运行!这个JS片段需要进行修订吗? - MarcoZen

0

从稍微不同的角度来看,这个 bug 似乎是由浏览器的建议功能引起的。既然我并不真正需要这些建议,我已经使用了:

if(/Android/.test(navigator.appVersion)){
  $('input[type="text"]').attr('autocomplete', "off");
}

这将提供更流畅的体验。


你有一个纯JS的解决方案吗? - MarcoZen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接