57

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.

7
  • Did you see this: stackoverflow.com/questions/13820088/… Commented Jul 27, 2016 at 17:40
  • @Iceman no I didn't. I tried searching for similar questions but didn't find that one. I'll try it. Commented Jul 27, 2016 at 17:41
  • @rescuecreative . I am not to sure if this will help, as this solution is for IOS8 as i had similar issues on IOS8 and Safari. Commented Jul 27, 2016 at 17:48
  • 1
    @Iceman 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. Commented Jul 27, 2016 at 17:49
  • @rescuecreative could be issue with new version. i'll remove my answer. Commented Jul 27, 2016 at 17:50

10 Answers 10

32

First

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

Then this

input.onfocus = function () { window.scrollTo(0, 0) document.body.scrollTop = 0 } 
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks! It works perfectly. Btw, I only use document.body.scrollTop = 0;
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.
Why is the first part i.e. e.preventDefault() on touchmove necessary?
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 setTimeout
8

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

1 Comment

Maybe one can add an empty dependency array to the useEffect just to make it run once.
5

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.

2 Comments

I feel like this is one of the most criminally underrated answers in all of StackOverflow. This behaviour was driving me absolutely bonkers with my Angular app on mobile and hours upon hours of Googling led to bizarre, overengineer solutions. This simple change fixed it immediately with no adverse effects. Thank you.
Worked like magic!!!! been looking for a solution for ages! Thank you.
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.

1 Comment

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

CSS,

html, body { overflow-y: auto; overflow-x: hidden; position: fixed; }

2 Comments

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

On the latest version of iOS (18.11) this was the only thing that worked for me inside of a PWA.

function renderAfterIOSKeyboardClosed(event) { // After a keyboard is closed on IPAD MINI 6TH GENERATION iOS 18.1.1 there is a large blank space left after pushing the content up. // This detects the keybord closing, and waits for the paint cycle to trigger a repaint by scrolling // Only apply to inputs and textareas if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') { console.log('Element blurred:', event.target); // Use requestAnimationFrame to wait for the next paint cycle requestAnimationFrame(function () { // If the target was pushed out of view, scroll it into view. event.target.scrollIntoView({ behavior: 'instant', block: 'end' }); }); } } // Attach globally for all blur events document.addEventListener('blur', renderAfterIOSKeyboardClosed, true); // Use capturing phase for delegation 

Comments

0

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

window.scrollBy(0, 0) 

Comments

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

Comments

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

4 Comments

Sorry, maybe I'm not understanding. How will the buttons and alerts help?
@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.
Ah, I see. No, I'm trying to stop the virtual keyboard from pushing the DOM outside of the viewport
@rescuecreative just a sggestion have you tried using these two options mentioned below?
-5

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

input.onfocus = function () { this.blur(); this.focus(); } 

1 Comment

This approach will hang the device. Imagine blurring and refocussing on the same element for an infinite time.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.