-
-
Save MartijnHols/e9f4f787efa9190885a708468f63c5bb to your computer and use it in GitHub Desktop.
The latest version is available at https://martijnhols.nl/gists/how-to-get-document-height-ios-safari-osk |
import { useEffect } from 'react' | |
const useOnScreenKeyboardScrollFix = () => { | |
useEffect(() => { | |
const handleScroll = () => { | |
window.scrollTo(0, 0) | |
} | |
window.addEventListener('scroll', handleScroll) | |
return () => { | |
window.removeEventListener('scroll', handleScroll) | |
} | |
}, []) | |
} | |
export default useOnScreenKeyboardScrollFix |
Thanks, Martijn 🙏
Hi @MartijnHols,
First of all, thank you so much for your work!!!
I have the same issue on my app and I implemented your solution and it works but most of the time when the keyboard is open and the screen resizes + the scroll to top emitting, there is a glitch that the app slides from the bottom to the top...
Here is an example, first focus has the glitch and the second don't:
Screen.Recording.2024-06-23.at.12.45.24.mov
@MartijnHols Thank you very much for the gists.
I also can't figure out a scroll issue.
I did everything as you suggested but this keeps happening ( testing on android for now )
GIF about the problem

FullViewportContainer
import { ElementType, HTMLAttributes } from "react";
import useViewportSize from "@/hooks/useViewportSize";
import useOnScreenKeyboardScrollFix from "@/hooks/useOnScreenKeyboardScrollFix";
import usePreventOverScrolling from "@/hooks/usePreventOverScrolling";
import useIsOnScreenKeyboardOpen from "@/hooks/useOnScreenKeyboardOpen";
interface Props extends HTMLAttributes<HTMLDivElement> {
element?: ElementType;
}
export function FullViewportContainer({
element: Element = "div",
...others
}: Props) {
useOnScreenKeyboardScrollFix();
const [, viewportHeight] = useViewportSize() ?? [];
const ref = usePreventOverScrolling();
const isOnScreenKeyboardOpen = useIsOnScreenKeyboardOpen();
return (
<Element
{...others}
ref={ref}
style={{
height: viewportHeight,
padding: isOnScreenKeyboardOpen
? "env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)"
: "env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)",
transition: "padding 100ms, height 100ms",
}}
/>
);
}
The only thing I left out is the css variables because I couldn't find them anywhere.
The problem is when I focus on the textarea, it drops the whole viewport up. If I scroll up manually it works.
Hi @MartijnHols, First of all, thank you so much for your work!!! I have the same issue on my app and I implemented your solution and it works but most of the time when the keyboard is open and the screen resizes + the scroll to top emitting, there is a glitch that the app slides from the bottom to the top... Here is an example, first focus has the glitch and the second don't:
If I understand the issue correctly, it's subtle and fixes itself a few frames later. Mine did the same, but the issue was hard to notice in production so I never did anything to fix it. The scroll-hack is kind of an ugly solution for squashing anything to do with scrolling, and I fear it would get too complex with too many edge cases if it were to be made any smarter.
@komagroup I mostly worked on the keyboard opening scroll hack in iOS, so most of my knowledge is about that. If I remember correctly, I noticed that the keyboard-scroll is kind of weird and not really a standard browser scroll so it's hard to work with. In your case it seems the scroll event might not be fired at all? You may have to do some logging to ensure the scroll effect is triggered. If it is, the event may be triggered before the browser actually scrolls and you may have to add a requestAnimationFrame(() => window.scrollTo(0, 0))
to delay the scroll position fix by a frame. If there's no scroll events, your browser is crazy and you'll have to do some digging. Worst case you will need to set a timer when the OSK opens, or poll the window scroll position (if the scroll is even happening to the window, because as I said, it doesn't appear to be a standard scroll).
The scroll hack of this gist isn't the prettiest and if I'm honest just one of the first things I threw at the problem that stuck. It worked perfectly fine on the devices I tested (mostly iOS but also a cheap and slow Motorola), but with the amount of (mobile) browsers there are, I would not be surprised there are some that misbehave (and browsers may change their behavior over time).
Yap. It doesn't even fire. It behaves very strange when I inspect it in devtools with a usb cable attached
Even the full html is resized but no event happens
import { useEffect } from "react";
const useOnScreenKeyboardScrollFix = () => {
useEffect(() => {
const handleScroll = () => {
console.log("FIRED");
window.scrollTo(0, 0);
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
};
export default useOnScreenKeyboardScrollFix;
I have added this line
useEffect(() => {
window.scrollTo(0, 0)
}, [viewportHeight]);
So it always scrolls to 0 if the viewportHeight changes but it also doesnt work.
Browser is standard android google chrome btw
You might need a few timeouts like I did in the useViewportSize
hook, but that will be kind of ugly for the user. Luckily it will only be ugly for the crazy browsers.
// Closing the OSK in iOS does not immediately update the visual viewport
// size :<
setTimeout(updateViewportSize, 1000)
One nice thing is that since you're setting scroll position to 0 every time, you can fairly easily start multiple timeouts without the user noticing.
It seems to work if I adjust the maxHeight instead of the height and it has a h-full class from tailwind ( which is just height:100% )
import { ElementType, HTMLAttributes } from "react";
import useViewportSize from "@/hooks/useViewportSize";
import useOnScreenKeyboardScrollFix from "@/hooks/useOnScreenKeyboardScrollFix";
import usePreventOverScrolling from "@/hooks/usePreventOverScrolling";
import useIsOnScreenKeyboardOpen from "@/hooks/useOnScreenKeyboardOpen";
interface Props extends HTMLAttributes<HTMLDivElement> {
element?: ElementType;
}
export function FullViewportContainer({
element: Element = "div",
...others
}: Props) {
useOnScreenKeyboardScrollFix();
const [, viewportHeight] = useViewportSize() ?? [];
const ref = usePreventOverScrolling();
const isOnScreenKeyboardOpen = useIsOnScreenKeyboardOpen();
return (
<Element
{...others}
ref={ref}
className="w-full h-full"
style={{
maxHeight: viewportHeight,
padding: isOnScreenKeyboardOpen
? "env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)"
: "env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)",
transition: "padding 100ms, height 100ms",
}}
/>
);
}
Aaaand it must be the direct children of the root it seems
Little tricky. Sucks that mobile-PWA development is so harsh sometimes. But this solution works like a charm. Love it. Many thanks!
when i use useOnScreenKeyboardScrollFix, the scroll content in FullViewportContainer will stop scroll, so i can not scroll into view on focusin
how can i get keyboard height
@dylan0805 This is pretty hacky; at the limits of what is reasonable to do in a browser. I'd only use if you really need to.