How to prevent iOS keyboard from pushing the view off screen with CSS or JS
Asked Answered
F

9

53

I have a responsive web page that opens a modal when you tap a button. When the modal opens, it is set to take up the full width and height of the page using fixed positioning. The modal also has an input field in it.

On iOS devices, when the input field is focused, the keyboard opens. However, when it opens, it actually pushes the full document up out of the way such that half of my page goes above the top of the viewport. I can confirm that the actual html tag itself has been pushed up to compensate for the keyboard and that it has not happened via CSS or JavaScript.

Has anyone seen this before and, if so, is there a way to prevent it, or reposition things after the keyboard has opened? It's a problem because I need users to be able to see content at the top of the modal while, simultaneously, I'd like to auto-focus the input field.

Fart answered 27/7, 2016 at 17:38 Comment(7)
Did you see this: #13820588Vinegarish
@Vinegarish no I didn't. I tried searching for similar questions but didn't find that one. I'll try it.Fart
@Fart . I am not to sure if this will help, as this solution is for IOS8 as i had similar issues on IOS8 and Safari.Oldenburg
@Vinegarish My environment is not phonegap, it's just a normal website. So I'm not sure if there's something different about phonegap, but this doesn't work for me.Fart
@Fart could be issue with new version. i'll remove my answer.Vinegarish
Set document.body.style.height = window.innerHeight+'px' on page load and try...Carolanncarole
This should help: blog.opendigerati.com/…Neuroglia
O
31

first

<script type="text/javascript">
 $(document).ready(function() {
     document.ontouchmove = function(e){
          e.preventDefault();
          }
 });

then this

input.onfocus = function () {
    window.scrollTo(0, 0);
    document.body.scrollTop = 0;
}
Oldenburg answered 27/7, 2016 at 18:54 Comment(4)
Thanks! It works perfectly. Btw, I only use document.body.scrollTop = 0;Smithson
I was trying to solve a problem for 4 hours until I encountered your answer. Thank you so much. Although only the input.onfocus = function () { window.scrollTo(0, 0); document.body.scrollTop = 0; } is necessary. The first part freezes the page.Lobectomy
Why is the first part i.e. e.preventDefault() on touchmove necessary?Broomfield
Also, it seems like the iOS "shove to top" behavior doesn't happen instantly, so I need to do a setTimeout() before actually resetting the window scroll. Now I'm trying to figure out the exact timing so I don't need to use setTimeoutBroomfield
I
7

For anyone stumbling into this in React, I've managed to fix it adapting @ankurJos solution like this:

const inputElement = useRef(null);

useEffect(() => {
  inputElement.current.onfocus = () => {
    window.scrollTo(0, 0);
    document.body.scrollTop = 0;
  };
});

<input ref={inputElement} />
Immixture answered 20/9, 2019 at 13:47 Comment(1)
Maybe one can add an empty dependency array to the useEffect just to make it run once.Grandsire
T
4

I struggled with this for awhile, I couldn't find something that worked well for me.

I ended up doing some JavaScript hackery to make it work.

What I found was that Safari wouldn't push the viewport if the input element was in the top half of the screen. That was the key to my little hack:

I intercept the focus event on the input object and instead redirect the focus to a invisible (by transform: translateX(-9999px)). Then once the keyboard is on screen (usually 200ms or so) I trigger the focus event on the original element which has since animated on screen.

It's a kind of complicated interaction, but it works really well.

function ensureOffScreenInput() {
  let elem = document.querySelector("#__fake_input");
  if (!elem) {
    elem = document.createElement("input");
    elem.style.position = "fixed";
    elem.style.top = "0px";
    elem.style.opacity = "0.1";
    elem.style.width = "10px";
    elem.style.height = "10px";
    elem.style.transform = "translateX(-1000px)";
    elem.type = "text";
    elem.id = "__fake_input";
    document.body.appendChild(elem);
  }
  return elem;
}

var node = document.querySelector('#real-input')
var fakeInput = ensureOffScreenInput();

function handleFocus(event) {
  fakeInput.focus();

  let last = event.target.getBoundingClientRect().top;

  setTimeout(() => {
    function detectMovement() {
      const now = event.target.getBoundingClientRect().top;
      const dist = Math.abs(last - now);

      // Once any animations have stabilized, do your thing
      if (dist > 0.01) {
        requestAnimationFrame(detectMovement);
        last = now;
      } else {
        event.target.focus();
        event.target.addEventListener("focus", handleFocus, { once: true });
      }
    }
    requestAnimationFrame(detectMovement);
  }, 50);
}

node.addEventListener("focus", handleFocus, { once: true });

Personally I use this code in a Svelte action and it works really well in my Svelte PWA clone of Apple Maps.

Video of it working in a PWA clone of Apple Maps

You'll notice in the video that the auto-complete changes after the animation of the input into the top half of the viewport stabilizes. That's the focus switch back happening.

The only downside of this hack is that the focus handler on your original implementation will run twice, but there are ways to account for that with metadata.

Turn answered 25/11, 2020 at 4:59 Comment(2)
This was the only thing worked for me. Thank youPrivation
Maybe I did the setup wrong but I couldn't get this to working with lambdatest.com real device test. The code was in an iframe attached to inputs also in the iframe and console.log prints were correct on input focus handlers and input was indeed shifting to the fake element but keyboard still pushed the page.Egotism
L
1

CSS,

html,
body {
  overflow-y: auto;
  overflow-x: hidden;
  position: fixed;
}
Liveryman answered 2/5, 2023 at 6:47 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Ent
After many other attempts, position: fixed on our react apps #root div did the trick. Thank you. Not sure if this will have some side effects for our app, but we'll seeDeathblow
C
0

you could also do this if you don't want scrollTo the top(0, 0)

window.scrollBy(0, 0)
Careycarfare answered 13/11, 2019 at 3:12 Comment(0)
M
0
const handleResize = () => {
  document.getElementById('header').style.top = window.visualViewport.offsetTop.toString() + 'px'
}

if (window && window.visualViewport) visualViewport.addEventListener('resize', handleResize)

Source: https://rdavis.io/articles/dealing-with-the-visual-viewport

Maegan answered 23/1, 2023 at 9:56 Comment(0)
M
0

Turns out, all you have to do is add

position:fixed

To the body tag. This will move the body back down when the virtual keyboard hides.

Mongolic answered 21/3 at 19:40 Comment(0)
O
-5

Both IOS8 and Safari bowsers behave the same for input.focus() occuring after page load. They both zoom to the element and bring up the keyboard.(Not too sure if this will be help but have you tried using something like this?)

HTML IS

<input autofocus>

JS is

for (var i = 0; i < 5; i++) {
document.write("<br><button onclick='alert(this.innerHTML)'>" + i + "</button>");
}

//document.querySelector('input').focus();

CSS

button {
width: 300px;
height: 40px;
}

ALso you will have to use a user-agent workaround, you can use it for all IOS versions

if (!/iPad|iPhone|iPod/g.test(navigator.userAgent)) {
element.focus();
}
Oldenburg answered 27/7, 2016 at 17:54 Comment(4)
Sorry, maybe I'm not understanding. How will the buttons and alerts help?Fart
@Fart i guess i have understood it wrong. I was thinking that maybe you are trying to show virtual keyboard and scroll after page touch.Oldenburg
Ah, I see. No, I'm trying to stop the virtual keyboard from pushing the DOM outside of the viewportFart
@Fart just a sggestion have you tried using these two options mentioned below?Oldenburg
D
-5

In some situations this issue can be mitigated by re-focusing the input element.

input.onfocus = function () {
  this.blur();
  this.focus();
}
Dorsiventral answered 11/4, 2019 at 10:45 Comment(1)
This approach will hang the device. Imagine blurring and refocussing on the same element for an infinite time.Pino

© 2022 - 2024 — McMap. All rights reserved.