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

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.
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.

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

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

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

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

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/

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

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.

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.

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

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.

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

Related

Openlayers 3 popover putting each word on one line

Working on an OL3 map, and am getting stuck on popover style. I haven't added any CSS to change it at all and it is currently being handled by default Bootstrap settings.
Okay so the problem: each word is being displayed on it's own line. How can I change this? Or, a better question, how do I control the style of the popovers?
This is was it looks like now: http://i.imgur.com/lrLYbag.png
The code handling popups:
//popups
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: 'bottom-center',
stopEvent: false
});
map.addOverlay(popup);
// display popup on hover
map.on('pointermove', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('name')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
});
Thanks!
I think you just need to add some css to the #popup element you're using for the popup.
Something like:
#popup {
min-width: 280px;
}
Take a look at this example http://openlayers.org/en/master/examples/popup.html for styling ideas.

CSS max-height transition double animation bug in webkit

See this JSFiddle for example: http://jsfiddle.net/6ocawwqd/21/
Stack Overflow is insisting I include the code that I'm linking to, so here is the JS and CSS:
$(document).on('click', '.show', function () {
$('.reveal')[0].style.removeProperty('display');
var height = $('.reveal')[0].scrollHeight;
$('.reveal').css({ 'max-height': height, 'overflow':'hidden' });
$('.reveal').removeClass('hide');
setTimeout(function() {
$('.reveal')[0].style.removeProperty('overflow');
$('.reveal')[0].style.removeProperty('max-height');
}, 501);
});
$(document).on('click', '.hide', function () {
var height = $('.reveal')[0].scrollHeight;
$('.reveal').css({'max-height': height, 'overflow':'hidden' });
setTimeout(function() {
$('.reveal').addClass('hide');
}, 5);
setTimeout(function() {
$('.reveal').css('display', 'none');
}, 505);
});
CSS
a {
color:blue;
cursor:pointer;
}
.reveal {
width:250px;
background-color:#ccc;
padding:10px;
transition: all .5s;
overflow:hidden;
translate3d(0,.01%,0);
}
.reveal.hide {
max-height:0 !important;
padding-top:0;
padding-bottom:0;
}
The Basic Problem:
I have a widget that I need to hide/show which has an unknown height. When hidden, the display must be set to 'none' to avoid tab index and form validation issues. So I'm using max-height property to make use of CSS transitions to animate the hiding and showing, and to swap the display from 'none' to 'block' (or just to the default by removing the display property from the element. The issue described happens in either case).
In my testing, I get a double animation only in OSX Safari, Chrome and Safari on iOS, and Android Stock mobile browser. (It works in Windows Chrome, FF, IE11, Android Chrome)
I've pinpointed when the double animation happens.
The first animation is correct, and happens when the max-height property is changed via JavaScript from 0 to whatever the content height is.
The second animation occurs when I then use a timer to remove the max-height property after the animation is complete. I must remove the max height, because after visible, the element may get even more items added to it and so must be allowed to grow.
Has anyone encountered this or have a solution?
I've encountered a similar issue, but found that a lot of the backface-visibility: hidden suggestions haven't resolved it for iOS.
As you're already using JavaScript to set/unset the height properties, you can try to toggle an additional 'animating' class to the element before animating and after the animation's complete. If you do this prior to removing the height (or setting it back to 'auto'), iOS won't be trying to re-animate the height property that results in the flicker.
Taking your example http://jsfiddle.net/m2adrugn/2/:
$(document).on('click', '.show', function () {
$('.reveal')[0].style.removeProperty('display');
var height = $('.reveal')[0].scrollHeight;
$('.reveal').css({ 'max-height': height, 'overflow':'hidden' });
$('.reveal').addClass('animating').removeClass('hide');
setTimeout(function() {
$('.reveal').removeClass('animating');
$('.reveal')[0].style.removeProperty('overflow');
$('.reveal')[0].style.removeProperty('max-height');
}, 501);
});
$(document).on('click', '.hide', function () {
var height = $('.reveal')[0].scrollHeight;
$('.reveal').addClass('animating').css({'max-height': height, 'overflow':'hidden' });
setTimeout(function() {
$('.reveal').addClass('hide');
}, 5);
setTimeout(function() {
$('.reveal').css('display', 'none').removeClass('animating');
}, 505);
});
CSS
a {
color:blue;
cursor:pointer;
}
.reveal {
width:250px;
background-color:#ccc;
padding:10px;
overflow:hidden;
translate3d(0,.01%,0);
}
.reveal.animating {
transition: all .5s;
}
.reveal.hide {
max-height:0 !important;
padding-top:0;
padding-bottom:0;
}
Change max-height to height.
Worked for me.

Input field prevents page from scrolling on iphone

Im working on a site that has a lot of forms. I have noticed that when you scroll/swipe the site on an iphone the page does not scroll if your finger is on an input field. To scroll you have to aim your finger on the body kind of.
Anyone having some ideas on what causes this strange behaviour?
Its just simple inputs wrapped in a form tag.
A workaround for this issue might be :
function setTextareaPointerEvents(value) {
var nodes = document.getElementsByTagName('textarea');
for(var i = 0; i < nodes.length; i++) {
nodes[i].style.pointerEvents = value;
}
}
document.addEventListener('DOMContentLoaded', function() {
setTextareaPointerEvents('none');
});
document.addEventListener('touchstart', function() {
setTextareaPointerEvents('auto');
});
document.addEventListener('touchmove', function() {
e.preventDefault();
setTextareaPointerEvents('none');
});
document.addEventListener('touchend', function() {
setTimeout(function() {
setTextareaPointerEvents('none');
}, 0);
});
This will make Mobile Safari (others not tested so far) ignore the textareas for scrolling but allows to set focus etc. as usual.

Issue with ios hover event

I am currently having issues making iPad work with a :hover event. To clear up what i mean i have uploaded part of the website at http://playing.everythingcreative.co.uk and I have 3 images, that when hovered over a div disappears to show text underneath but this does not work on the iPad at all. I tried:
ontouchstart="touchStart(event);"
But i don't know enough on how it works to use it right.
Any help would be great.
I figured it out anyway using the example on iOS automatic hover fix? and changing:
if(navigator.platform == "iPad") {
to:
if ("ontouchstart" in document.documentElement) {
The final code:
$(document).ready(function() {
if ("ontouchstart" in document.documentElement) {
$("div").each(function() { // have to use an `each` here - either a jQuery `each` or a `for(...)` loop
var onClick; // this will be a function
var firstClick = function() {
onClick = secondClick;
return false;
};
var secondClick = function() {
onClick = firstClick;
return true;
};
onClick = firstClick;
$(this).click(function() {
return onClick();
});
});
}
});

iOS/WebKit: touchmove/touchstart not working on input & textarea

I have an IOS Web App that can't be scrolled. For that reason I want to deactivate scrolling. To do this, I use an element's ontouchmove attribute and have it call a function that uses element.preventDefault.
However, I am unable to detect any touching event when it starts on a textarea or input element, even when the element is disabled! I have also tried binding the touchmove or touchstart event to these elements via JavaScript's addEventlistener, without success!
And here's my JavaScript:
function onBodyLoad() {
document.addEventListener( "touchstart", doNotScroll, true );
document.addEventListener( "touchmove", doNotScroll, true );
}
function doNotScroll( event ) {
event.preventDefault();
event.stopPropagation();
}
Thanks for any help!
I think I've found a great workaround for this issue using the "pointer-events" CSS property:
function setTextareaPointerEvents(value) {
var nodes = document.getElementsByTagName('textarea');
for(var i = 0; i < nodes.length; i++) {
nodes[i].style.pointerEvents = value;
}
}
document.addEventListener('DOMContentLoaded', function() {
setTextareaPointerEvents('none');
});
document.addEventListener('touchstart', function() {
setTextareaPointerEvents('auto');
});
document.addEventListener('touchmove', function(e) {
e.preventDefault();
setTextareaPointerEvents('none');
});
document.addEventListener('touchend', function() {
setTimeout(function() {
setTextareaPointerEvents('none');
}, 0);
});
This will make Mobile Safari on iOS (others not tested so far) ignore the textareas for scrolling but allows to set focus etc. as usual.
the Thomas Bachem answer is the one!
I rewrote it in jQuery. Just add a class scrollFix to your desired inputs and you are ready to go. Or attach the event handlers to all inputs and textareas using $('input, textarea').
Now when you touch and scroll on an input on iOS 8+, the input get all its pointer-events disabled (including the problematic behavior). Those pointer-events are enabled when we detect a simple touch.
$('.scrollFix').css("pointer-events","none");
$('body').on('touchstart', function(e) {
$('.scrollFix').css("pointer-events","auto");
});
$('body').on('touchmove', function(e) {
$('.scrollFix').css("pointer-events","none");
});
$('body').on('touchend', function(e) {
setTimeout(function() {
$('.scrollFix').css("pointer-events", "none");
},0);
});

Resources