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

41,378

Solution 1

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

Solution 2

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

Solution 3

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.

Share:
41,378
rescuecreative
Author by

rescuecreative

Updated on November 25, 2020

Comments

  • rescuecreative
    rescuecreative over 3 years

    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.

  • rescuecreative
    rescuecreative almost 8 years
    Sorry, maybe I'm not understanding. How will the buttons and alerts help?
  • ankurJos
    ankurJos almost 8 years
    @rescuecreative i guess i have understood it wrong. I was thinking that maybe you are trying to show virtual keyboard and scroll after page touch.
  • rescuecreative
    rescuecreative almost 8 years
    Ah, I see. No, I'm trying to stop the virtual keyboard from pushing the DOM outside of the viewport
  • ankurJos
    ankurJos almost 8 years
    @rescuecreative just a sggestion have you tried using these two options mentioned below?
  • Nguyen Tran
    Nguyen Tran almost 7 years
    Thanks! It works perfectly. Btw, I only use document.body.scrollTop = 0;
  • neophyte
    neophyte over 6 years
    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.
  • Sujit Kumar Singh
    Sujit Kumar Singh about 5 years
    This approach will hang the device. Imagine blurring and refocussing on the same element for an infinite time.
  • Onat
    Onat about 3 years
    This was the only thing worked for me. Thank you