Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Preventing iOS Safari scrolling when focusing on input elements
<!--
When an input element gets focused, iOS Safari tries to put it in the center by scrolling (and zooming.)
Zooming can be easily disabled using a meta tag, but the scrolling hasn't been quite easy.
The main quirk (I think) is that iOS Safari changes viewport when scrolling; i.e., toolbars shrink.
Since the viewport _should_ change, it thinks the input _will_ move, so it _should_ scroll, always.
Even times when it doesn't need to scroll—the input is fixed, all we need is the keyboard—
the window always scrolls _up and down_ resulting in some janky animation.
However, iOS Safari doesn't scroll when the input **has opacity of 0 or is completely clipped.**
We can make use of this behavior. There are two possible workarounds, as shown below.
An upside to this approach beside being simple is that sniffing the User Agent string
to recognize iOS Safari might be no longer needed.
See it in action:
Before: https://twitter.com/kid1ng/status/1356167756622643200
After: https://twitter.com/kid1ng/status/1356166043169738762
https://twitter.com/kid1ng/status/1356176495991906305
See also:
Adobe's react-aria solves this problem in an incredibly spectacular way.
For more context and details, do take a look at the code and the comments at:
https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/usePreventScroll.ts
https://twitter.com/devongovett/status/1310652121063198720
https://twitter.com/jordwalke/status/1355681285717385217
-->
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
#modal {
position: fixed;
background: gray;
top: 2em;
right: 2em;
left: 2em;
height: 10em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#methodTwoWrapper {
position: relative;
}
</style>
<body>
<section>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque facilisis tellus pulvinar mauris lobortis, vel commodo erat finibus. Duis et ex eget nulla suscipit condimentum. Cras egestas arcu quis turpis congue vestibulum. Vestibulum et elit efficitur neque mollis semper. Vestibulum ut nisl dolor. Maecenas tempor nec tortor fringilla elementum. Sed eu porta orci. Proin mi enim, rhoncus quis pretium eu, posuere a ex. Mauris ac augue porta, laoreet urna nec, faucibus odio. Phasellus tempor sollicitudin velit a suscipit. Cras aliquam imperdiet ex vitae gravida. Curabitur hendrerit ante vel dolor maximus, at rhoncus mauris pulvinar. Etiam venenatis metus diam, at varius nisi elementum sit amet. Donec at rutrum odio. Integer vitae hendrerit orci, in molestie lacus. Ut cursus ipsum eu ultrices lacinia.</p>
<p>Nunc vitae varius neque, sit amet vehicula magna. Nunc lacinia, odio a aliquet rhoncus, metus turpis semper orci, eu tincidunt leo nisi id magna. Suspendisse quis dui velit. Pellentesque quis commodo mi. Suspendisse mauris dui, sagittis auctor orci vitae, laoreet hendrerit odio. Vivamus feugiat sapien erat, ornare tincidunt dui imperdiet a. Pellentesque accumsan quam ut consectetur ultrices. In porttitor nibh quis ligula fermentum efficitur. Maecenas a dui quis arcu fermentum porta nec a tellus. Integer accumsan finibus nisi. Nam suscipit sed risus nec mattis. Sed ut dui vel lectus vestibulum mattis.</p>
<p>Fusce iaculis pulvinar elit, in molestie leo efficitur a. Morbi vehicula lectus ac tortor fringilla sodales. Vestibulum commodo mollis euismod. Suspendisse gravida nisi mauris, nec varius nunc vulputate vel. Quisque vel tincidunt neque, nec rhoncus nisi. Sed et velit efficitur, porta purus in, accumsan urna. Etiam mauris odio, viverra accumsan convallis quis, convallis a odio. Nullam hendrerit massa turpis, et lobortis mi egestas eu.</p>
<p>Nulla ex justo, molestie id turpis non, porta luctus nibh. Nunc sollicitudin justo id velit maximus rhoncus. Vestibulum ut quam sit amet tellus eleifend dictum quis nec tellus. Donec faucibus, velit ac gravida lacinia, sem urna luctus sem, nec porttitor libero nisl et libero. Sed molestie ut nunc vitae tincidunt. Phasellus facilisis gravida odio non interdum. Nullam et varius dui, vel interdum orci. Proin sit amet ante lobortis, bibendum ex sodales, iaculis dui. Mauris tristique augue et elit sodales fermentum. Integer vulputate tellus arcu, ut maximus nunc imperdiet ac. Vestibulum imperdiet sem quis maximus euismod.</p>
</section>
<section id="modal">
<input placeholder="No workaround">
<input placeholder="Method 1" id="methodOne">
<div id="methodTwoWrapper">
<input placeholder="Method 2" id="methodTwo">
</div>
</section>
<script>
window.addEventListener("DOMContentLoaded", () => {
setTimeout(() => window.scrollTo(0, 100));
/*
* Method 1: Briefly change the opacity.
* Element might "blink" on focus in some scenarios.
*/
methodOne.addEventListener("focus", () => {
methodOne.style.opacity = 0;
setTimeout(() => methodOne.style.opacity = 1);
});
/*
* Method 2: Stack a cloned input element on top.
* Requires a relatively-positioned wrapper div.
* Might not be applicable to some scenarios.
*/
methodTwo.addEventListener("focus", () => {
const cloneTwo = methodTwo.cloneNode();
cloneTwo.removeAttribute("id");
cloneTwo.style.position = "absolute";
cloneTwo.style.top = 0;
cloneTwo.style.left = 0;
cloneTwo.style.opacity = 0;
methodTwoWrapper.append(cloneTwo);
cloneTwo.focus();
setTimeout(() => {
cloneTwo.style.opacity = 1;
methodTwo.style.opacity = 0;
methodTwo.disabled = true;
});
cloneTwo.addEventListener("blur", () => {
methodTwo.value = cloneTwo.value;
methodTwo.disabled = false;
methodTwo.style.opacity = 1;
cloneTwo.style.opacity = 0;
setTimeout(() => cloneTwo.remove());
});
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment