Skip to content

Instantly share code, notes, and snippets.

@Lukas1h
Last active November 26, 2025 20:20
Show Gist options
  • Select an option

  • Save Lukas1h/92432ce6d4e73165e36401022b289f1b to your computer and use it in GitHub Desktop.

Select an option

Save Lukas1h/92432ce6d4e73165e36401022b289f1b to your computer and use it in GitHub Desktop.
iOS Keyboard Handeling in SvelteKit
<script lang="ts">
import { store } from '$lib/store/store.svelte';
import { isIOS } from '$lib/utils';
// Keyboard and scroll logic for iOS.
// Does the following:
// - Uses heuristics to guess if the keyboard's open and it's height.
// - Prevents scrolling below the keyboard.
// - Closes the keyboard when scrolling up.
onMount(() => {
if (!isIOS()) return;
if (window.visualViewport) {
let height = window.visualViewport?.height || 0;
let keyboardOpenedDate = 0;
let isIntervalRunning = false;
// Watch for viewport size changes.
window.visualViewport.addEventListener('resize', (e) => {
const newHeight = window.visualViewport?.height || 0;
const heightDifference = height - newHeight;
const textareaFocused =
document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA';
if (store.keyboardHeight == undefined && textareaFocused) {
// If the viewport height changed while a textarea was
// focused for the first time, then the keyboard was probably opened.
store.keyboardHeight = heightDifference;
} else if (
store.keyboardHeight != undefined &&
heightDifference < store.keyboardHeight &&
textareaFocused
) {
// Sometimes the viewport height will change to ramdon things
// when scrolling, but the difference is always greater then the keyboard's height.
// This updates the keyboard height if it were to ever change for some reason.
// Probably don't need this, but it's here for robustness.
store.keyboardHeight = heightDifference;
}
// If the viewport just shrank and a textarea is focused,
// then the keyboard was probably opened.
if (
newHeight < height &&
textareaFocused &&
!store.isKeyboardOpen &&
window.scrollY <= (store.keyboardHeight || 0)
) {
keyboardOpenedDate = Date.now();
store.isKeyboardOpen = true;
console.log('====. keyboard opened!!');
}
if (
newHeight > height &&
store.isKeyboardOpen &&
window.scrollY <= (store.keyboardHeight || 0)
) {
store.isKeyboardOpen = false;
console.log('====. keyboard closed!!');
}
});
// Watch for scrolling.
window.addEventListener('scroll', (e) => {
console.log('scrolled');
// Window resizing also triggers scrolling,
// so wait 100ms until after the resizing when
// the keyboard is hopefully done animating.
// If the keyboard is open, and the window is scrolled
// past the input into the blank area, then scroll back up
// so the blank area isn't taking up space.
if (store.keyboardHeight && store.isKeyboardOpen && window.scrollY > store.keyboardHeight) {
window.scrollTo(0, store.keyboardHeight);
}
if (!isIntervalRunning) {
isIntervalRunning = true;
// When the keyboard is opened, it resized the viewport
// and also trigers the scroll handeler, so we need to wait
// 500ms until after the keyboard has finished opening to
// run this check.
setTimeout(() => {
isIntervalRunning = false;
// If the keyboard is open, and the window is scrolled
// up, then close the keyboard.
if (
store.keyboardHeight &&
store.isKeyboardOpen &&
window.scrollY < store.keyboardHeight
) {
(document.activeElement as HTMLInputElement)?.blur();
}
}, 500);
}
});
}
});
</script>
class Store {
keyboardHeight: number | undefined = $state(undefined)
isKeyboardOpen = $state(false)
}
export const store = new Store()
export function isIOS() {
return (
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment