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.
document.body.style.height = window.innerHeight+'px'
on page load and try... – Carolanncarole