21

When a mobile browser brings up a keyboard it tries to move the scrollbars so that the input is still in view.

On iOS Safari it seems to do this properly by finding the nearest scrolling parent.

On Android native or Chrome mobile browser it seems to just try the body element and then gives up, so the focused input is hidden beneath the keyboard.

 How to break it

Set overflow-y: hidden on the body element. Create a scrollable container and put a form in there.

When you select an element near the bottom of your screen it will be obscured by the keyboard.

Demo

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>

Any ideas how to fix this? Will it require some browser detection and messy hacks?

Community
  • 1
  • 1
Dominic
  • 62,658
  • 20
  • 139
  • 163

4 Answers4

51

This is a bug in the Android native browser. By the way, the input scrolls into the view after a character is typed on the soft keyboard.

The following code snippet placed somewhere in the page should help:

if(/Android 4\.[0-3]/.test(navigator.appVersion)){
   window.addEventListener("resize", function(){
      if(document.activeElement.tagName=="INPUT"){
         window.setTimeout(function(){
            document.activeElement.scrollIntoViewIfNeeded();
         },0);
      }
   })
}
Serge
  • 641
  • 7
  • 6
  • 3
    Note that Android 4.4 and 5 needs this too, not only "4.[0-3]" – pauloya Jan 19 '15 at 16:23
  • On a page I am working on, the input would be scrolled into view correctly about 95% of the time, but once in a while it seemed not to scroll at all. (Chrome version 43 on Android 5.1). scrollIntoViewIfNeeded seems to force the needed scroll to occur in those cases. So maybe the bug is also seen in Chrome on Android? The original question also mentions both the stock Android browser and Chrome. – Coleman Jul 09 '15 at 19:10
  • @Serge MDN says not to use this in production > https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded – Rahul Gupta Jan 12 '17 at 11:17
  • 2
    You should rather [`document.activeElement.scrollIntoView()`](https://developer.mozilla.org/en/docs/Web/API/Element/scrollIntoView) which is better supported by other browsers also. – Rahul Gupta Jan 12 '17 at 11:28
  • We've found on certain android devices when an input is focused, then focusing on another input by touch causes the webview to scroll up. This solution resolved this issue aswel. – Steven Mark Ford May 18 '17 at 17:15
  • Does any of you guys know what bug is it on bugzilla (if it's a webkit bug) or on any other bug tracking tool used by Slick / Chrome? – UndefinedBehavior Jan 12 '18 at 13:39
9

The answer from Serge is great but I had a few modifications that improved it for me.

The problem appeared on Android 6 for me as well so I added it to the check and I needed the fix to work for textareas as well as inputs.

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);
      }
   })
}

If anyone needs the fix in Angular 1, here is what I used there.

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();
                });
            }
        });
    }
});
Zack Huston
  • 426
  • 5
  • 6
  • Can you explain how this is working ? Specifically, why we are adding it to the `resize` event because for a mobile screen it will never be resized right ? – Rahul Gupta Jan 12 '17 at 11:07
  • 1
    @RahulGupta On android keyboard opening triggered the resize event. I don't believe resize is triggered on iOS with the keyboard opening but we just needed to fix android here. – Zack Huston Jan 17 '17 at 14:56
  • It's not working for "Select" tag. I tried using if(document.activeElement.tagName == 'INPUT' || document.activeElement.tagName == 'TEXTAREA' || document.activeElement.tagName == 'SELECT') { document.activeElement.scrollIntoView(); } – Jyoti Duhan Oct 26 '21 at 13:51
6

Offering slight revision if it saves anyone some time:

  • 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();
         }
      })
    } 
    
Derek Mueller
  • 71
  • 1
  • 3
  • 1
    `setTimeout` is really need, I've tested that way it's more stable! – James Yang Sep 16 '17 at 12:50
  • @Derek Mueller - 2020 - And the above JS snippet ( no jquery ) still works on a Chrome 78.x on a Android Phone ! Is there any revision to this JS snippet ? – MarcoZen Apr 21 '20 at 15:49
0

Looking at this slightly differently the bug seems to be caused by the Suggestions feature of the browser. As I don't really want the suggestions anyway I've used:

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

which gives a much smoother experience.

Ben Trewern
  • 1,583
  • 1
  • 10
  • 13