How to stop browser auto scrolling containers on focus changes

8.2k views Asked by At

As you tab between input fields in a browser, the browser will automatically scroll the nearest parent container to place the next focused field within the view.

Simple JSFiddle: http://jsfiddle.net/TrueBlueAussie/pxyXZ/1/

$('.section').eq(6).find('input').focus();

For example if you open the above fiddle it selects "Sample item 7" at the bottom of the yellow window. If you press tab the "Sample text 8" field jumps up towards the middle of the parent window.

Obviously this is a great thing for normal websites, but I have a custom scrolling container in which I position & scroll everything manually. I am tracking focus changes and will use a momentum scroller to bring it into view, but how do I disable the default scrolling behavior of web-browsers? Happy to accept CSS, Javascript or JQuery solutions.

2

There are 2 answers

2
iCollect.it Ltd On BEST ANSWER

Turns out you can't smooth scroll for focus changes as the events happen in the wrong order. You get an awful delay while it scrolls the field into view, before focus is set. A better move of the item onscreen, or superfast scroll, is all we can hope for.

As suggested by PlantTheIdea (+1'ed), you need to catch the TAB key and find the next focusable item, bring it into view, then set focus to it.

In practice there are a number of issues to resolve:

  • Change of focus occurs on TAB keydown (not keyup).
  • Only match non-hidden inputs (lots of web apps have hidden fields that will go bang if you try to focus them).
  • Allow for the selection to tab off the first or last item on the page (otherwise the browser loses the ability to tab to its address bar)
  • use e.keyCode || e.which to allow for older browsers
  • catch event at document level to allow for cases of other inputs, outside of the scrolling area, causing it to enter the scrolling area (first or last input).

The final code looks like this:

$(document).on('keydown', ':focus', function (event)
{
    if ((event.keyCode || event.which) == 9)
    {
        var $inputs = $(":input:not(hidden)")
        var index = $inputs.index(this);
        // Index previous or next input based on the shift key
        index += event.shiftKey ? -1 : 1;
        // If we are in the range of valid inputs (else browser takes focus)
        if (index >= 0 && index < $inputs.length)
        {
            var $next = $inputs.eq(index);
            event.preventDefault();
            // Move, not scroll, to the next/prev item....
            MoveIntoView($next);
            $next.focus();
            return false;
        }
    }
});
3
PlantTheIdea On

This is just winging it based on my comment above:

$('input').on('keyup',function(e){
    if(e.keyCode === 9) {
        var $this = $(this);

        // (do your scroll thing here
        // ..., function(){
            $this.parent().next().find('input').focus();
        // });
    }
});

Long as the callback timing is correct, this will only change focus after you have already scrolled. You'll need to do your own magic to determine what to scroll to, but this should give you the focus behavior you want.