iOS 5 fixed positioning and virtual keyboard
Asked Answered
R

25

139

I have a mobile website which has a div pinned to the bottom of the screen via position:fixed. All works fine in iOS 5 (I'm testing on an iPod Touch) until I'm on a page with a form. When I tap into an input field and the virtual keyboard appears, suddenly the fixed position of my div is lost. The div now scrolls with the page as long as the keyboard is visible. Once I click Done to close the keyboard, the div reverts to its position at the bottom of the screen and obeys the position:fixed rule.

Has anyone else experienced this sort of behavior? Is this expected? Thanks.

Round answered 1/11, 2011 at 17:32 Comment(8)
Yes, it seems Apple didn't think this one through so well for IOS5. Any fixed position elements become relative to the page as soon as the virtual keyboard appears. It would probably be OK if the elements reverted to an absolute position as this wouldn't break the layout. Unfortunately the actual placement of these elements is far less predictable. I have this exact problem with my fixed header on [REDACTED]. Scroll down the page, then click on the search box and bang... layout broken. I've even tried to fix it by reverting to absolute positioning on the focus event, which works but then I losBalinese
I've run into this same problem. Has anyone file a bug with Apple to see about getting this fixed? Also, has anyone else seen this behavior continue in iOS6?Giffie
I'm running into the same problem with iOS6.Torey
Did you ever come up with a solution? I have a fixed header that gets all messed up when focusing in a textarea field. I'm testing on iOS6, and it's still problematic.Torey
The same problem still seems to exist in iOS7!Turboprop
Doesn't seem to be fixed in iOS 8...Sung
Ok guys, any relevant solution?Kirstiekirstin
Hi all , any solutions ??Depreciate
C
49

I had this problem in my application. Here's how I'm working around it:

input.on('focus', function(){
    header.css({position:'absolute'});
});
input.on('blur', function(){
    header.css({position:'fixed'});
});

I'm just scrolling to the top and positioning it there, so the iOS user doesn't notice anything odd going on. Wrap this in some user agent detection so other users don't get this behavior.

Cadastre answered 21/3, 2013 at 1:23 Comment(7)
Literally stumbled upon this issue today and this seemed the most reasonable, +1.Homemaking
This works pretty well. I only call this for iOS devices: var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);Torey
I also removed $(window).scrollTop(0)... I don't think it's needed and it caused unwanted scrolling.Torey
Or simply document.body.scrollTop = 0 if you're not using jQuery and targeting latest browsers.Clerkly
On longer forms, setting the $(window).scrollTop(0); can cause the focused input to be moved off-screen e.g. an input further down the page, or if an iPhone is in portrait. A better solution is on focus set the header.css({position:'absolute',top:window.pageYOffset+'px'}); and on blur set header.css({position:'fixed',top:0});.Unbearable
Ok I've removed the scrolltop part. I suppose it would scroll there anyway since it wants to put the focused element on the screen. I haven't tested this though, and I have a feeling if you scrolled down a bunch that it wouldn't actually scroll back up correctly...Cadastre
@NickRetallack hi Nick, what language is this ? It looks a bit like jquery but without the $(""). etc. .cheersAfricander
B
16

I had a slightly different ipad issue where the virtual keyboard pushed my viewport up offscreen. Then after the user closed the virtual keyboard my viewport was still offscreen. In my case I did something like the following:

var el = document.getElementById('someInputElement');
function blurInput() {
    window.scrollTo(0, 0);
}
el.addEventListener('blur', blurInput, false);
Bergstrom answered 27/9, 2012 at 21:35 Comment(2)
Doesn't help for me:(Ezraezri
@Altaveron. sorry to hear that. It was for a specific issue I was having on ios6 and lower devices. I have not revisited since.Bergstrom
C
14

This is the code we use to fix problem with ipad. It basically detect discrepancies between offset and scroll position - which means 'fixed' isn't working correctly.

$(window).bind('scroll', function () {
    var $nav = $(".navbar")
    var scrollTop = $(window).scrollTop();
    var offsetTop = $nav.offset().top;

    if (Math.abs(scrollTop - offsetTop) > 1) {
        $nav.css('position', 'absolute');
        setTimeout(function(){
            $nav.css('position', 'fixed');
        }, 1);
    }
});
Confluent answered 5/2, 2013 at 11:14 Comment(3)
It's from jquery.timers plugin. You can use window.setTimeout instead.Confluent
triggering this same function on input blur and focus will also help.Nato
Genius -- really dig this. Also +1 to @Blowsie's suggestion of using this of focus and blur instead of scroll.Hereditable
J
12

The position fixed elements simply don't update their position when the keyboard is up. I found that by tricking Safari into thinking that the page has resized, though, the elements will re-position themselves. It's not perfect, but at least you don't have to worry about switching to 'position: absolute' and tracking changes yourself.

The following code just listens for when the user is likely to be using the keyboard (due to an input being focused), and until it hears a blur it just listens for any scroll events and then does the resize trick. Seems to be working pretty well for me thus far.

    var needsScrollUpdate = false;
    $(document).scroll(function(){
        if(needsScrollUpdate) {
            setTimeout(function() {
                $("body").css("height", "+=1").css("height", "-=1");
            }, 0);
        }
    });
    $("input, textarea").live("focus", function(e) {
        needsScrollUpdate = true;
    });

    $("input, textarea").live("blur", function(e) {
        needsScrollUpdate = false;
    });
Jessalyn answered 5/2, 2012 at 20:45 Comment(2)
This didn't actually work for me on the iPad. I ended up changing the fixed footer's position to relative and have the footer at the bottom of the page until the input blur event was detected when I changed the position back to fixed.Pachydermatous
Doesn't work anymore, iOS probably fixed whatever bug made this workHibernal
D
6

Just in case somebody happens upon this thread as I did while researching this issue. I found this thread helpful in stimulating my thinking on this issue.

This was my solution for this on a recent project. You just need to change the value of "targetElem" to a jQuery selector that represents your header.

if(navigator.userAgent.match(/iPad/i) != null){

var iOSKeyboardFix = {
      targetElem: $('#fooSelector'),
      init: (function(){
        $("input, textarea").on("focus", function() {
          iOSKeyboardFix.bind();
        });
      })(),

      bind: function(){
            $(document).on('scroll', iOSKeyboardFix.react);  
                 iOSKeyboardFix.react();      
      },

      react: function(){

              var offsetX  = iOSKeyboardFix.targetElem.offset().top;
              var scrollX = $(window).scrollTop();
              var changeX = offsetX - scrollX; 

              iOSKeyboardFix.targetElem.css({'position': 'fixed', 'top' : '-'+changeX+'px'});

              $('input, textarea').on('blur', iOSKeyboardFix.undo);

              $(document).on('touchstart', iOSKeyboardFix.undo);
      },

      undo: function(){

          iOSKeyboardFix.targetElem.removeAttr('style');
          document.activeElement.blur();
          $(document).off('scroll',iOSKeyboardFix.react);
          $(document).off('touchstart', iOSKeyboardFix.undo);
          $('input, textarea').off('blur', iOSKeyboardFix.undo);
      }
};

};

There is a little bit of a delay in the fix taking hold because iOS stops DOM manipulation while it is scrolling, but it does the trick...

Defection answered 28/2, 2013 at 18:13 Comment(3)
This works great up until iOS 7. Anyone else having issues with the element being positioned properly but disappearing?Offshore
@RonaKilmer Change the init into a regular function (not self-invoking; it's firing too early). Then call iOSKeyboardFix.init(); just before the end of the if statement.Salisbarry
@Salisbarry Thanks, I implemented that awhile back but that's not my issue. There is something else going on. One of my fixed elements disappears when it is repositioned. The other one doesn't. I can't blame that on the keyboard fix since it's not happening everywhere. I haven't seen that anyone else is having the same issue either so I'll have to dig deeper.Offshore
J
4

None of the other answers I've found for this bug have worked for me. I was able to fix it simply by scrolling the page back up by 34px, the amount mobile safari scrolls it down. with jquery:

$('.search-form').on('focusin', function(){
    $(window).scrollTop($(window).scrollTop() + 34);
});

This obviously will take effect in all browsers, but it prevents it breaking in iOS.

Jezabelle answered 30/8, 2012 at 6:39 Comment(0)
M
4

This issue is really annoying.

I combined some of the above mentioned techniques and came up with this:

$(document).on('focus', 'input, textarea', function() {
    $('.YOUR-FIXED-DIV').css('position', 'static');
});

$(document).on('blur', 'input, textarea', function() {
    setTimeout(function() {
        $('.YOUR-FIXED-DIV').css('position', 'fixed');
        $('body').css('height', '+=1').css('height', '-=1');
    }, 100);
});

I have two fixed navbars (header and footer, using twitter bootstrap). Both acted weird when the keyboard is up and weird again after keyboard is down.

With this timed/delayed fix it works. I still find a glitch once in a while, but it seems to be good enough for showing it to the client.

Let me know if this works for you. If not we might can find something else. Thanks.

Micromho answered 3/7, 2013 at 10:1 Comment(1)
You don't seem to need that timeout (at least it was working fine for me without), and if also happens on select elements, other than that this solution works great.Hatten
G
4

I was experiencing same issue with iOS7. Bottom fixed elements would mess up my view not focus properly.

All started working when I added this meta tag to my html.

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,height=device-height" >

The part which made the difference was:

height=device-height

Hope that helps someone.

Georgeannageorgeanne answered 17/8, 2014 at 2:5 Comment(1)
Thanks @pasevin. This fixed my issue in iOS 9.3.2Adenoidal
Q
3

I've taken Jory Cunningham answer and improved it:

In many cases, it's not just one element who goes crazy, but several fixed positioned elements, so in this case, targetElem should be a jQuery object which has all the fixed elements you wish to "fix". Ho, this seems to make the iOS keyboard go away if you scroll...

Needless to mention you should use this AFTER document DOM ready event or just before the closing </body> tag.

(function(){
    var targetElem = $('.fixedElement'), // or more than one
        $doc       = $(document),
        offsetY, scrollY, changeY;

    if( !targetElem.length || !navigator.userAgent.match(/iPhone|iPad|iPod/i) )
        return;

    $doc.on('focus.iOSKeyboardFix', 'input, textarea, [contenteditable]', bind);

    function bind(){
        $(window).on('scroll.iOSKeyboardFix', react);
        react();
    }

    function react(){
        offsetY = targetElem.offset().top;
        scrollY = $(window).scrollTop();
        changeY = offsetY - scrollY;

        targetElem.css({'top':'-'+ changeY +'px'});

        // Instead of the above, I personally just do:
        // targetElem.css('opacity', 0);

        $doc.on('blur.iOSKeyboardFix', 'input, textarea, [contenteditable]', unbind)
            .on('touchend.iOSKeyboardFix', unbind);
    }

    function unbind(){
        targetElem.removeAttr('style');
        document.activeElement.blur();

        $(window).off('scroll.iOSKeyboardFix');
        $doc.off('touchend.iOSKeyboardFix blur.iOSKeyboardFix');
    }
})();
Quake answered 8/6, 2014 at 16:4 Comment(2)
@Quake apologies, i had no idea you were a 'master chef'Pampas
when select,input focus in that time header is moving up(not visible).I want fixed the header div when focusing select and input fileds. any one please help meLissalissak
F
2

I have a solution similar to @NealJMD except mine only executes for iOS and correctly determines the scroll offset by measuring the scollTop before and after the native keyboard scrolling as well as using setTimeout to allow the native scrolling to occur:

var $window = $(window);
var initialScroll = $window.scrollTop();
if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
  setTimeout(function () {
    $window.scrollTop($window.scrollTop() + (initialScroll - $window.scrollTop()));
  }, 0);
}
Fatling answered 11/11, 2013 at 22:51 Comment(0)
G
1

I have fixed my Ipad main layout content fixed position this way:

var mainHeight;
var main = $('.main');

// hack to detects the virtual keyboard close action and fix the layout bug of fixed elements not being re-flowed
function mainHeightChanged() {
    $('body').scrollTop(0);
}

window.setInterval(function () {
    if (mainHeight !== main.height())mainHeightChanged();
    mainHeight = main.height();
}, 100);
Gine answered 1/7, 2013 at 21:52 Comment(2)
This worked really well for me in a complicated JQUery UI Dialog and On-Screen Keyboard mixup that left my layout halfway down the screen when dialog and keyboard are closed at the same time, though with a couple of edits: var main = $('body div').first(); ...and... function mainHeightChanged() { $(top).scrollTop(0); }Yardage
setInterval... really? it's better just to stay with the bug than doing thisQuake
M
1

I had a similar problem to @ds111 s. My website was pushed up by the keyboard but didn't move down when the keyboard closed.

First I tried @ds111 solution but I had two input fields. Of course, first the keyboard goes away, then the blur happens (or something like that). So the second input was under the keyboard, when the focus switched directly from one input to the other.

Furthermore, the "jump up" wasn't good enough for me as the whole page only has the size of the ipad. So I made the scroll smooth.

Finally, I had to attach the event listener to all inputs, even those, that were currently hidden, hence the live.

All together I can explain the following javascript snippet as: Attach the following blur event listener to the current and all future input and textarea (=live): Wait a grace period (= window.setTimeout(..., 10)) and smoothly scroll to top (= animate({scrollTop: 0}, ...)) but only if "no keyboard is shown" (= if($('input:focus, textarea:focus').length == 0)).

$('input, textarea').live('blur', function(event) {
    window.setTimeout(function() {
        if($('input:focus, textarea:focus').length == 0) {
            $("html, body").animate({ scrollTop: 0 }, 400);
        }
    }, 10)
})

Be aware, that the grace period (= 10) may be too short or the keyboard may still be shown although no input or textarea is focused. Of course, if you want the scrolling faster or slower, you may adjust the duration (= 400)

Manley answered 31/8, 2013 at 13:19 Comment(0)
C
1

really worked hard to find this workaround, which in short looks for focus and blur events on inputs, and scrolling to selectively change the positioning of the fixed bar when the events happen. This is bulletproof, and covers all cases (navigating with <>, scroll, done button). Note id="nav" is my fixed footer div. You can easily port this to standard js, or jquery. This is dojo for those who use power tools ;-)

define([ "dojo/ready", "dojo/query", ], function(ready, query){

ready(function(){

    /* This addresses the dreaded "fixed footer floating when focusing inputs and keybard is shown" on iphone 
     * 
     */
    if(navigator.userAgent.match(/iPhone/i)){
        var allInputs = query('input,textarea,select');
        var d = document, navEl = "nav";
        allInputs.on('focus', function(el){
             d.getElementById(navEl).style.position = "static";
        });

        var fixFooter = function(){
            if(d.activeElement.tagName == "BODY"){
                d.getElementById(navEl).style.position = "fixed";
            }
        };
        allInputs.on('blur', fixFooter);
        var b = d.body;
        b.addEventListener("touchend", fixFooter );
    }

});

}); //end define

Cheerleader answered 26/9, 2013 at 16:10 Comment(2)
This might be awesome... porting to jQuery/js & will report back. As an aside, solutions in niche scripting language choices that require porting or adding nonstandard tools to a project are, predictably, not going to be found as universally helpful.Geotaxis
This will not work for every case because this will scroll you to the 'static' position of the element, which could be miles away from your input's original position, especially if it's in a fixed container. If the container has an overflow:hidden rule, you won't see your input at all as it is now outside the box.Melisent
S
1

This is a difficult problem to get 'right'. You can try and hide the footer on input element focus, and show on blur, but that isn't always reliable on iOS. Every so often (one time in ten, say, on my iPhone 4S) the focus event seems to fail to fire (or maybe there is a race condition), and the footer does not get hidden.

After much trial and error, I came up with this interesting solution:

<head>
    ...various JS and CSS imports...
    <script type="text/javascript">
        document.write( '<style>#footer{visibility:hidden}@media(min-height:' + ($( window ).height() - 10) + 'px){#footer{visibility:visible}}</style>' );
    </script>
</head>

Essentially: use JavaScript to determine the window height of the device, then dynamically create a CSS media query to hide the footer when the height of the window shrinks by 10 pixels. Because opening the keyboard resizes the browser display, this never fails on iOS. Because it's using the CSS engine rather than JavaScript, it's much faster and smoother too!

Note: I found using 'visibility:hidden' less glitchy than 'display:none' or 'position:static', but your mileage may vary.

Skid answered 25/11, 2013 at 8:44 Comment(1)
You might need to switch between height and width to fix this for both portrait and landscape modes.Pitcher
S
1

Works for me

if (navigator.userAgent.match(/iPhone|iPad|iPod/i)) {
    $(document).on('focus', 'input, textarea', function() {
        $('header').css({'position':'static'});
    });
    $(document).on('blur', 'input, textarea', function() {
        $('header').css({'position':'fixed'});
    });
}
Soria answered 13/3, 2014 at 11:19 Comment(0)
S
1

In our case this would fix itself as soon as user scrolls. So this is the fix we've been using to simulate a scroll on blur on any input or textarea:

$(document).on('blur', 'input, textarea', function () {
    setTimeout(function () {
        window.scrollTo(document.body.scrollLeft, document.body.scrollTop);
    }, 0);
});
Sawbuck answered 10/7, 2014 at 7:23 Comment(0)
P
1

My answer is that it can't be done.

I see 25 answers but none work in my case. That's why Yahoo and other pages hide the fixed header when the keyboard is on. And Bing turns the whole page non-scrollable (overflow-y: hidden).

The cases discussed above are different, some have issues when scrolling, some on focus or blur. Some have fixed footer, or header. I can't test now each combination, but you might end up realizing that it can't be done in your case.

Plasmosome answered 9/2, 2017 at 9:6 Comment(2)
This doesn't answer the question.Syreetasyria
It does, since the answer is that there is no solution.Plasmosome
D
0

Found this solution on Github.

https://github.com/Simbul/baker/issues/504#issuecomment-12821392

Make sure you have scrollable content.

// put in your .js file
$(window).load(function(){
    window.scrollTo(0, 1);
});

// min-height set for scrollable content
<div id="wrap" style="min-height: 480px">
  // website goes here
</div>

The address bar folds up as an added bonus.

Detour answered 17/4, 2013 at 7:46 Comment(0)
D
0

In case anyone wanted to try this. I got the following working for me on a fixed footer with an inputfield in it.

<script>
    $('document').ready(
        function() {
            if (navigator.userAgent.match(/Android/i) || navigator.userAgent.match(/webOS/i) || navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)
                  || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/BlackBerry/i) || navigator.userAgent.match(/Windows Phone/i)) {
                var windowHeight = $(window).height();
                var documentHeight = $(document).height();

                $('#notes').live('focus', function() {
                    if (documentHeight > windowHeight) {
                        $('#controlsContainer').css({
                            position : 'absolute'
                        });
                        $("html, body").animate({
                            scrollTop : $(document).height()
                        }, 1);
                    }
                });
                $('#notes').live('blur', function() {
                    $('#controlsContainer').css({
                        position : 'fixed'
                    });
                    $("html, body").animate({
                        scrollTop : 0
                    }, 1);
                });
            }
        });
</script>
Donnettedonni answered 15/5, 2013 at 15:29 Comment(0)
S
0

I have the same issue. But I realized that the fixed position is just delayed and not broken (at least for me). Wait 5-10 seconds and see if the div adjusts back to the bottom of the screen. I believe it's not an error but a delayed response when the keyboard is open.

Sombrous answered 24/6, 2013 at 21:57 Comment(0)
C
0

I tried all the approaches from this thread, but if they didn't help, they did even worse. In the end, I decided force device to loose focus:

$(<selector to your input field>).focus(function(){
    var $this = $(this);
    if (<user agent target check>) {
        function removeFocus () {
            $(<selector to some different interactive element>).focus();
            $(window).off('resize', removeFocus);
        }
        $(window).on('resize', removeFocus);
    }
});

and it worked like a charm and fixed my sticky login-form.

Please NOTE:

  1. The JS code above is only to present my idea, to execute this snippet please replace values in angular braces (<>) with appropriate values for your situation.
  2. This code is designed to work with jQuery v1.10.2
Canning answered 19/7, 2013 at 16:24 Comment(0)
T
0

This is still a large bug for for any HTML pages with taller Bootstrap Modals in iOS 8.3. None of the proposed solutions above worked and after zooming in on any field below the fold of a tall modal, Mobile Safari and/or WkWebView would move the fixed elements to where the HTML body's scroll was situated, leaving them misaligned with where they actually where laid out.

To workaround the bug, add an event listener to any of your modal inputs like:

$(select.modal).blur(function(){
  $('body').scrollTop(0);
});

I'm guessing this works because forcing the HTML body's scroll height re-aligns the actual view with where the iOS 8 WebView expects the fixed modal div's contents to be.

Teresiateresina answered 28/5, 2015 at 20:46 Comment(0)
S
0

If anybody was looking for a completely different route (like you are not even looking to pin this "footer" div as you scroll but you just want the div to stay at the bottom of the page), you can just set the footer position as relative.

That means that even if the virtual keyboard comes up on your mobile browser, your footer will just stay anchored to the bottom of the page, not trying to react to virtual keyboard show or close.

Obviously it looks better on Safari if position is fixed and the footer follows the page as you scroll up or down but due to this weird bug on Chrome, we ended up switching over to just making the footer relative.

Sext answered 28/8, 2015 at 0:15 Comment(0)
S
0

None of the scrolling solutions seemed to work for me. Instead, what worked is to set the position of the body to fixed while the user is editing text and then restore it to static when the user is done. This keeps safari from scrolling your content on you. You can do this either on focus/blur of the element(s) (shown below for a single element but could be for all input, textareas), or if a user is doing something to begin editing like opening a modal, you can do it on that action (e.g. modal open/close).

$("#myInput").on("focus", function () {
    $("body").css("position", "fixed");
});

$("#myInput").on("blur", function () {
    $("body").css("position", "static");
});
Sachikosachs answered 16/1, 2016 at 0:49 Comment(0)
M
0

iOS9 - same problem.

TLDR - source of the problem. For solution, scroll to bottom

I had a form in a position:fixed iframe with id='subscribe-popup-frame'

As per the original question, on input focus the iframe would go to the top of the document as opposed to the top of the screen.

The same problem did not occur in safari dev mode with user agent set to an idevice. So it seems the problem is caused by iOS virtual keyboard when it pops up.

I got some visibility into what was happening by console logging the iframe's position (e.g. $('#subscribe-popup-frame', window.parent.document).position() ) and from there I could see iOS seemed to be setting the position of the element to {top: -x, left: 0} when the virtual keyboard popped up (i.e. focussed on the input element).

So my solution was to take that pesky -x, reverse the sign and then use jQuery to add that top position back to the iframe. If there is a better solution I would love to hear it but after trying a dozen different approaches it was the only one that worked for me.

Drawback: I needed to set a timeout of 500ms (maybe less would work but I wanted to be safe) to make sure I captured the final x value after iOS had done its mischief with the position of the element. As a result, the experience is very jerky . . . but at least it works

Solution

        var mobileInputReposition = function(){
             //if statement is optional, I wanted to restrict this script to mobile devices where the problem arose
            if(screen.width < 769){
                setTimeout(function(){
                    var parentFrame = $('#subscribe-popup-frame',window.parent.document);
                    var parentFramePosFull = parentFrame.position();
                    var parentFramePosFlip = parentFramePosFull['top'] * -1;
                    parentFrame.css({'position' : 'fixed', 'top' : parentFramePosFlip + 'px'});
                },500);
            }    
        }   

Then just call mobileInputReposition in something like $('your-input-field).focus(function(){}) and $('your-input-field).blur(function(){})

Mousey answered 16/6, 2016 at 3:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.