iOS fix for position fixed elements on input focus (virtual keyboard visible)

Another approach to fix some behaviors of position fixed elements on iOS devices.

Update

I wrote a new post about iOS Safari jumps to the top of the page when form elements inside fixed positioned divs receive input.


Fixed positioned layout and content scrolling support arrived to MobileSafari with the release of iOS 5. But it’s support has some caveats as mentioned here and here. This articles include also solutions for the this caveats so you should definitely read them. And this caveats are still there in iOS 6.

Another thing that annoys me comes to play when the virtual keyboard displays, e.g. focus of an input element. Somehow fixed positioned elements lose their position, hang somewhere on the page and hide other elements. A lot of pages have a fixed navbar as a header at the top of page. On pages with forms this header could hide information users already put in and this a usability nightmare.

OK, moving the fixed element from the viewport is maybe a feature. The user sees more of the page as the fixed elements doesn’t occupy space any more. Well, maybe…

Example/Usecase and fix on: http://jsfiddle.net/kFTRn/1/embedded/result/.
You should test it on iOS device ;)

The fix

To fix this issue we will take advantage of the already defined styles of the involved elements. The goal is to have as little css and js manipulation as possible which keeps things simple and flexible. We will achieve this by simple changing the elements position from fixed to absolute. Thats it, you don’t have to manipulate any other elements nor add a scroll event handler.

Our HTML

Fixed position elements could be everywhere on the page, but this fix adresses headers and footers which are children of the body element.

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <div class="header">Header</div>
        <div class="container">
            <form action="">
                <label>Testfield: <input type="text" /></label>
            </form>
        </div>
        <div class="footer">Footer</div>
    </body>
</html>

Our CSS

The body has already some padding at top to leverage this space for the header. And in this example also at the bottom, so the footer can’t hide elements of the container. The header is already positioned top and has a z-index. So does the footer, but its position is bottom of course.

body { 
    padding: 120px 0 50px 0; 
} 
.header { 
    position: fixed; 
    height: 100px; 
    width: 100%; 
    top: 0; 
    z-index: 1000; 
    background: #bada55; 
} 
.footer { 
    position: fixed; 
    height: 30px; 
    width: 100%; 
    bottom: 0; 
    z-index: 1000; 
    background: #facade; 
} 
.fixfixed .header, 
.fixfixed .footer { 
    position: absolute; 
} 

Our JS

We could easily override the position:fixed declaration by adding a inline position:absolute declaration. But this destroys our flexibility as we can’t override inline styles without using the css !important declaration.

So we will use the body element as a controller and add a class to it which adresses the position change in our css.

/* we need this only on touch devices */
if (Modernizr.touch) {
    /* cache dom references */ 
    var $body = jQuery('body'); 

    /* bind events */
    $(document)
    .on('focus', 'input', function() {
        $body.addClass('fixfixed');
    })
    .on('blur', 'input', function() {
        $body.removeClass('fixfixed');
    });
} 

To conclude

The header and footer are moved offscreen when you focus an input element. This jump is hardly noticeable by the user as there is also the virtual keyboard sliding in. These elements also leverage more space on the viewport, so now you can call it a feature.

I hope you enjoyed this little tutorial.

26 Replies to “iOS fix for position fixed elements on input focus (virtual keyboard visible)”

    1. Hi D****r,

      that’s a good point.

      I suggest to change the inputs selector in JS to match only inputs in .container:

      /* change this */
      var $inputs = jQuery('input'); 
      /* to this */
      var $inputs = jQuery('.container').find('input');
      

      I hope this helps!

      1. That would actually setup a lot of listener. Using the bubbling characteristic of the event by attaching it only on the document makes it less power consuming. The initial solution also have the advantage of working on dynamically added field (after script run), yours doesn’t.

  1. Use the code below to have this apply to all current and future input type fields

    
    if (Modernizr.touch) {
        /* bind events */
        $(document)
        .on('focus', 'input', function(e) {
            $('body').addClass('fixfixed');
        })
        .on('blur', 'input', function(e) {
            $('body').removeClass('fixfixed');
        });
    }
    
  2. I love this snippet, one problem though.
    I am a chrome-guy, and that on my IOS-devices.. and they ignore this snippet ;/

    But works for safari though.

    As much as I hate all issues and problems when coding for web… how come we care about this stupid language ;)

  3. I really like this solution. I packaged it up into a little jQuery plugin so I could:
    – Set which parent gets the class
    – Set which elements this applies to (don’t forget “textarea” and “select”).
    – Set what the parent class name is
    – Allow it to be chained
    – Allow it to be used multiple times

    
    $.fn.mobileFix = function (options) {
        var $parent = $(this),
            $fixedElements = $(options.fixedElements);
    
        $(document)
        .on('focus', options.inputElements, function(e) {
            $parent.addClass(options.addClass);
        })
        .on('blur', options.inputElements, function(e) {
            $parent.removeClass(options.addClass);
    
            // Fix for some scenarios where you need to start scrolling
            setTimeout(function() {
                $(document).scrollTop($(document).scrollTop())
            }, 1);
        });
    
        return this; // Allowing chaining
    };
    
    // Only on touch devices
    if (Modernizr.touch) {
        $("body").mobileFix({ // Pass parent to apply to
            inputElements: "input,textarea", // Pass activation child elements
            addClass: "fixfixed" // Pass class name
        });
    }
    
  4. Hi dansajin

    you can enhance it like this:

    
    $(function() {
      var $body;
      if (Modernizr.touch) {
        $body = $("body");
        document.addEventListener('focusin', function() {
          return $body.addClass("fixfixed");
        });
        return document.addEventListener('focusout', function() {
          $body.removeClass("fixfixed");
          return setTimeout(function() {
            return $(window).scrollLeft(0);
          }, 20);
        });
      }
    });
    

    the focusin and focusout seems can indict the vitual keyboard’s behavior.

  5. A solution not involving modernizr, with no CSS involved and covering most of the mobile devices out there. Also, this patch will only apply on fixed navbar.

    
        // Fix mobile floating toolbar when input is focused
        if(/iPhone|iPod|Android|iPad/.test(window.navigator.platform)){
          $(document)
            .on('focus', 'textarea,input,select', function(e) {
              $('.navbar.navbar-fixed-top').css('position', 'absolute');
            })
            .on('blur', 'textarea,input,select', function(e) {
              $('.navbar.navbar-fixed-top').css('position', '');
            });
          }
    
    1. This was a life saver.
      Thanks!
      I realized that I also needed to assign the ‘window.pageYOffset’ to the ‘top’ property of the parent element, when switching position from fixed to absolute, and then resetting it when switching back to fixed.

    2. Is the intention here to scroll to the top of the page so that the nav does not move to the middle of the viewport?

  6. Hi, thanks for this tip.

    The problem I’m seeing is that when I remove the focus from the input (when I blur), even though the navbar is set back to position:fixed, the navbar is still shown in the middle of the screen until I scroll up or down, which is when it jumps to the top, where it should originally be.

    Mani Nilchiani seems to be referring to something similar, but I’m not quite sure I understand what he’s doing and I would like to avoid to programatically scroll to force the navbar to go to the top.

    Any ideas?
    Many thanks!

  7. Can somebody please share an example in vanilla JavaScript? Not very familiar with JQuery.

      1. I just tried this demo on an iPhone 6 and the top bar is not fixed whenever the keyboard is displayed. It’s like Safari enters a special mode that doesn’t understand position:fixed when the keyboard is visible :(.

  8. This may be required again with the latest release of Chrome 44, where position:fixed seems to be broken.

  9. Hi, I use Sticky header and nav fixed at top, it works fine in all but only in ios, sometimes it floats, can you please help me.

    Thanks
    Simran

    1. Hi Simran,
      I’m pretty sure you googeled for “ios navbar position fixed”.
      Can you create a codepen or something similar so I can have a look?

Comments are closed.