How do I stop my fixed navigation from moving like this when the virtual keyboard opens in Mobile Safari?

IosCssMobilePositionMobile Safari

Ios Problem Overview


I understand that mobile safari has a lot of bugs around fixed elements, but for the most part I've managed to get my layout working correctly until I added a much needed text input to the fixed navigation at the bottom. Now when the user focuses on the text input element and the virtual keyboard appears, my navigation, which is otherwise always fixed at the bottom of the page, jumps up to a really strange spot in the middle of the page.

enter image description here

I'd add some of my code to this post, but I wouldn't be sure where to start. That navigation is fixed at the bottom and positioned to the left and bottom 0, and 100% width. From there, I don't know what's going on, I can only assume it's a mobile safari bug.

It also appears to lose it's position fixed and become relative, only while the text input element is focused on and the virtual keyboard is open.

Ios Solutions


Solution 1 - Ios

http://dansajin.com/2012/12/07/fix-position-fixed/ this is one of the solutions proposed. Seems worth a shot.

In short: set fixed elements to position:absolute when any input is focused and reset them when that element is blurred

.header { 
    position: fixed; 
} 
.footer { 
    position: fixed; 
} 
.fixfixed .header, 
.fixfixed .footer { 
    position: absolute; 
} 

and

if ('ontouchstart' in window) {
    /* cache dom references */ 
    var $body = $('body'); 

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

Solution 2 - Ios

The solutions on the top are some ways to go and fix the problem, but I think adding extra css class or using moderniz we are complicating things.

If you want a more simple solution, here is a non-modernizr non-extra-css but pure jquery solution and work on every device and browsers I use this fix on all my projects

if ('ontouchstart' in window) {
    $(document).on('focus', 'textarea,input,select', function() {
        $('.navbar.navbar-fixed-top').css('position', 'absolute');
    }).on('blur', 'textarea,input,select', function() {
        $('.navbar.navbar-fixed-top').css('position', '');
    });
}

Solution 3 - Ios

I had a similar problem, but I found a workaround by adding the following css class to the body element on input focus and then removing it again on unfocus:

.u-oh {
    overflow: hidden;
    height: 100%;
    width: 100%;
    position: fixed;
}

Solution 4 - Ios

Taking from what sylowgreen did, the key is to fix the body on entering the input. Thus:

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

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

Solution 5 - Ios

Add javascript like this:

$(function() {
  var $body;
  if ('ontouchstart' in window) {
    $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);
    });
  }
});

and add class like this:

.fixfixed header{ 
    position: absolute; 
} 

you can reference this article: http://dansajin.com/2012/12/07/fix-position-fixed/

Solution 6 - Ios

I really like the solution above. 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

Code example:

$.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,select", // Pass activation child elements
        addClass: "fixfixed" // Pass class name
    });
}

Solution 7 - Ios

I use this jQuery script:

var focus = 0;
var yourInput = $(".yourInputClass");
yourInput.focusin(function(){
	if(!focus) {
		yourInput.blur();
		$("html, body").scrollTop($(document).height());
		focus = 1;
	}
	if(focus) {
		yourInput.focus();
		focus = 0;
	}
});

Works perfectly for me.

Solution 8 - Ios

The focusin and focusout events seem to be better suited to this problem than the focus and blur events since the former bubble up to the root element. See this answer on SO.

Personally I use AngularJS, so I implemented it like this:

$window.document.body.addEventListener('focusin', function(event) {
    var element = event.target;
    var tagName = element.tagName.toLowerCase();
    if(!$rootScope.inputOverlay && (tagName === 'input' || tagName === 'textarea' || tagName === 'select')) {
        $rootScope.$apply(function() {
            $rootScope.inputOverlay = true;
        });
    }
});
$window.document.body.addEventListener('focusout', function() {
    if($rootScope.inputOverlay) {
        $rootScope.$apply(function() {
            $rootScope.inputOverlay = false;
        });
    }
});

Note: I am conditionally running this script if this is mobile Safari.

I put an ng-class attribute on my navbar:

<div class="navbar navbar-default navbar-fixed-top" ng-class="{'navbar-absolute': inputOverlay}">

using the following CSS:

.navbar-absolute {
    position: absolute !important;
}

You can read more about focusin here and focusout here.

Solution 9 - Ios

Test this one. It works. I just test it.

$(document).on('focus','input', function() {
    setTimeout(function() {
        $('#footer1').css('position', 'absolute');
        $('#header1').css('position', 'absolute');
    }, 0);
});
$(document).on('blur','input', function() {
    setTimeout(function() {
        $('#footer1').css('position', 'fixed');
        $('#header1').css('position', 'fixed');
    }, 800);
});

Solution 10 - Ios

None of these solutions worked for me because my DOM is complicated and I have dynamic infinite scroll pages, so I had to create my own.

Background: I am using a fixed header and an element further down that sticks below it once the user scrolls that far down. This element has a search input field. In addition, I have dynamic pages added during forward and backwards scroll.

Problem: In iOS, anytime the user clicked on the input in the fixed element, the browser would scroll all the way to the top of the page. This not only caused undesired behavior, it also triggered my dynamic page add at the top of the page.

Expected Solution: No scroll in iOS (none at all) when the user clicks on the input in the sticky element.

Solution:

     /*Returns a function, that, as long as it continues to be invoked, will not
	be triggered. The function will be called after it stops being called for
	N milliseconds. If `immediate` is passed, trigger the function on the
	leading edge, instead of the trailing.*/
	function debounce(func, wait, immediate) {
	    var timeout;
	    return function () {
	        var context = this, args = arguments;
	        var later = function () {
	            timeout = null;
	            if (!immediate) func.apply(context, args);
	        };
	        var callNow = immediate && !timeout;
	        clearTimeout(timeout);
	        timeout = setTimeout(later, wait);
	        if (callNow) func.apply(context, args);
	    };
	};

     function is_iOS() {
	    var iDevices = [
		  'iPad Simulator',
		  'iPhone Simulator',
		  'iPod Simulator',
		  'iPad',
		  'iPhone',
		  'iPod'
	    ];
	    while (iDevices.length) {
	        if (navigator.platform === iDevices.pop()) { return true; }
	    }
	    return false;
	}

	$(document).on("scrollstop", debounce(function () {
	    //console.log("Stopped scrolling!");
	    if (is_iOS()) {
	        var yScrollPos = $(document).scrollTop();
	        if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
	            $('#searchBarDiv').css('position', 'absolute');
	            $('#searchBarDiv').css('top', yScrollPos + 50 + 'px'); //50 for fixed header
	        }
	        else {
	            $('#searchBarDiv').css('position', 'inherit');
	        }
	    }
	},250,true));

	$(document).on("scrollstart", debounce(function () {
	    //console.log("Started scrolling!");
	    if (is_iOS()) {
	        var yScrollPos = $(document).scrollTop();
	        if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
	            $('#searchBarDiv').css('position', 'fixed');
	            $('#searchBarDiv').css('width', '100%');
	            $('#searchBarDiv').css('top', '50px'); //50 for fixed header
	        }
	    }
	},250,true));

Requirements: JQuery mobile is required for the startsroll and stopscroll functions to work.

Debounce is included to smooth out any lag created by the sticky element.

Tested in iOS10.

Solution 11 - Ios

I wasn't having any luck with the solution proposed by Dan Sajin. Perhaps the bug has changed since he wrote that blog post, but on iOS 7.1, the bug will always surface when the position is changed back to fixed after the input is blurred, even if you delay until the software keyboard is hidden completely. The solution I came to involves waiting for a touchstart event rather than the blur event since the fixed element always snaps back into proper position when the page is scrolled.

if (Modernizr.touch) {
  var $el, focused;
  $el = $('body');
  focused = false;
  $(document).on('focus', 'input, textarea, select', function() {
    focused = true;
    $el.addClass('u-fixedFix');
  }).on('touchstart', 'input, textarea, select', function() {
    // always execute this function after the `focus` handler:
    setTimeout(function() {
      if (focused) {
        return $el.removeClass('u-fixedFix');
      }
    }, 1);
  });
}

HTH

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEricView Question on Stackoverflow
Solution 1 - IosSujesh ArukilView Answer on Stackoverflow
Solution 2 - IosrevobtzView Answer on Stackoverflow
Solution 3 - IossylowgreenView Answer on Stackoverflow
Solution 4 - IosScott SemyanView Answer on Stackoverflow
Solution 5 - Ioswxy112233View Answer on Stackoverflow
Solution 6 - IosmartinedwardsView Answer on Stackoverflow
Solution 7 - IosEdie JohnnyView Answer on Stackoverflow
Solution 8 - Iosbrain_baconView Answer on Stackoverflow
Solution 9 - IosHesamView Answer on Stackoverflow
Solution 10 - IosDimaView Answer on Stackoverflow
Solution 11 - IosPatrick CanfieldView Answer on Stackoverflow