Dealing with form elements in fixed layers on iOS

2018-08-20 | #css, #ios, #javascript, #solution, #webdev

Ah, iOS Safari, so we meet again.

One of the more obscure behaviours (among many) of iOS Safari is the infamous "let's scroll to that focused form element" problem. If the user activates a form element, by interacting with it, Safari deems it a good idea to move the viewport to center the focused element. That alone is bad enough, but if that element is in a fixed layer, pre 11.3, very strange things begin to happen.

Everyhing between "we scroll a little" and "our page layout dies a horrible death" might happen.

So, how do we rescue our page in a case where we have elements in fixed layers? That depends a little, if you have something like a sticky header, which does not fill the screen you might have no solution or at least not one without visual artifacts. In case we have a layer covering the viewport such as a lightbox we have better options.

The easiest thing you could try is a CSS fix, which would be the cleanest, most unhacky solution:

html,

body {

-webkit-overflow-scrolling : touch;

overflow: auto;

height: 100%;

}

The problem with this is the fact that these are very basic settings, possibly impossible to apply in old projects without having to rework the whole layout. In those cases we have to find another solution. There is a JS-based solution based on locking the scrolling for the time a fixed layer is displayed or the element inside it is focused by essentially setting the body to a state where we cannot scroll, combined with the saving and resetting of the current scroll position.

export function lockScroll(){

$('body')

.data('scroll-lock-scroll-top', $(window).scrollTop())

.addClass('scroll-locked')

.css('top', $(window).scrollTop())

;

}

export function unlockScroll(){

let $body = $('body');

$body

.removeClass('scroll-locked')

.css('top', '')

;

if( $body.data('scroll-lock-scroll-top') ){

window.scrollTo(0, parseInt($body.data('scroll-lock-scroll-top'), 10));

$body.removeData('scroll-lock-scroll-top');

}

}

With these functions we can lock the scrolling on display/focus and unlock it again afterwards. For this to work, we also need the following CSS of course:

body.scroll-locked {

overflow: hidden;

position: fixed;

left: 0;

width: 100%;

height: 100%;

}