
위와 같이 하단에 고정시킨 영역이 키보드가 열렸을 때 키보드 위에 위치하도록 구현해야 했다.
하지만 iOS에서 키보드가 열리면 position: fixed 요소가 키보드에 가려지는 문제가 자주 발생한다.
이 문제를 해결하면서 공부한 내용을 정리하고자 한다.
플랫폼 마다 다른 키보드 열림 시 페이지 처리방식
안드로이드와 ios는 가상키보드가 열릴 때 페이지를 차지하는 방식이 다른데,
- 안드로이드는 키보드가 열릴 때 viewport를 키보드를 제외한 영역만큼의 height를 설정해준다.
- ios는 viewport가 아닌 document를 키보드 영역 만큼 밀어버린다. → fixed 요소가 키보드 아래에 갇히는 문제 발생
여러가지 구현 방법이 있겠지만, 나는 키보드 높이와 키보드 유무에 따라 하단 요소의 위치를 변경해주는 식으로 해결하고자 했다.
(추후 서술하지만, 여기에 문제점도 존재한다 🥲)
VisualViewport API 이용하기
visualViewport는 사용자가 실제로 볼 수 있는 화면 영역에 대한 정보를 제공한다. 이 API를 활용하면 키보드가 열렸을 때 viewport의 시각적 변화를 감지할 수 있다.

1-1 키보드 상태 감지하기
키보드가 열렸는지 감지하기 위해 visualViewport.height와 window.innerHeight를 비교한다.
- `window.innerHeight` : 페이지의 전체 뷰포트 높이를 나타냅니다. 키보드가 열리더라도 이 값은 변경되지 않으며, 화면 전체의 논리적 크기를 기준으로 합니다.
- `visualViewport.height` : 사용자가 실제로 볼 수 있는 화면의 높이를 나타냅니다. 키보드가 열리면 키보드가 가시 영역 일부를 차지하므로 이 값은 키보드 높이만큼 감소합니다.
두 값의 차이가 발생하면 키보드가 열렸다고 판단 할 수 있다.
useEffect(() => {
const visualViewport = window.visualViewport;
const handleResize = () => {
if (!visualViewport) return;
const viewportHeight = visualViewport.height;
const isKeyboardVisible = viewportHeight < window.innerHeight;
setKeyboardOpen(isKeyboardVisible);
};
if (visualViewport) {
visualViewport.addEventListener("resize", handleResize);
}
return () => {
if (visualViewport) {
visualViewport.removeEventListener("resize", handleResize);
}
};
}, []);
+) 24.11.21 추가
fixed + bottom: 0 요소 내부에 포커스 가능한 요소가 있을 때, 키보드 열림 상태를 감지하지 못하는 이슈
위의 코드로 구현 시 키보드가 열릴 때도 `isKeyboardOn` 플래그가 false로 유지되는 문제가 있었다.
문제 상황을 따라가자면,
- 바텀시트같은 fixed 요소 내부에 focusable한 요소가 포함되어 있고, 포커스 시 브라우저가 화면을 스크롤함
- 이때 `window.innerHeight` 와 `window.visualViewport.height` 가 동일하게 측정되어, isKeyboardOn가 false로 설정됨
문제 원인은 (Safari)브라우저의 기본동작 때문이였다.
Safari는 입력 필드가 화면에 노출되도록 자동으로 스크롤한다.
기존 로직에서는`window.innerHeight` 와 `window.visualViewport.height` 를 비교해 키보드 열림 여부를 판단했다.
하지만 `window.innerHeight` 는 스크롤을 포함하지 않은 현재 가시 영역의 높이만 반환하므로, `visualViewport.height`와 동일한 값을 가지게 되는 것이다.
`window.innerHeight` 에 스크롤 높이(window.scrollY)를 합산하여 스크롤을 포함한 전체 화면 높이를 계산하도록 수정하면 된다!
const isKeyboardVisible = viewportHeight < window.innerHeight + window.scrollY;
1-2 키보드 높이를 활용해 하단 요소 이동시키기
키보드가 열리면 전체 viewport 높이와 시각적 viewport의 차이를 계산하여 키보드의 높이를 구한다.
이 값을 이용해 하단 요소를 키보드 위로 이동시킨다.
const fixedElStyle: CSSProperties = {
position: "fixed",
bottom: isKeyboardOpen ? `calc(100% - ${viewportHeight}px)` : "0",
left: 0,
right: 0,
transition: isIOS
? `bottom ${KEYBOARD_ANIMATION_DURATION}ms cubic-bezier(${KEYBOARD_ANIMATION_BEZIER.join(",")}), transform 0.1s ease-out`
: "bottom 0.1s ease-out",
opacity: isKeyboardOpen ? 1 : 0,
pointerEvents: state.isKeyboardOpen ? "auto" : "none",
};
isKeyboardOpen 상태를 기반으로 하단 요소의 bottom 값을 동적으로 설정했다.

짜잔 최종 결과물이다!
이제 키보드가 열리면 하단 고정 요소가 자연스럽게 키보드 위로 이동한다.
그리고 남아있는 문제점
현재 구현 방식은 실제 viewport가 줄어드는 것이 아니라 요소의 위치를 조정하는 방식이다.
ios 키보드가 열릴 때 애니메이션과 위치가 이동할 때의 애니메이션 싱크가 완벽히 일치하지 않아 어색하게 느껴지는 감이 있다.

그리고 스크롤 할 때도....ㅜㅜ
지금드는 생각으론, 스크롤 시 input blur 처리하는건 어떨까 싶기도 하다 🤔

위와 같이 하단에 고정시킨 영역이 키보드가 열렸을 때 키보드 위에 위치하도록 구현해야 했다.
하지만 iOS에서 키보드가 열리면 position: fixed 요소가 키보드에 가려지는 문제가 자주 발생한다.
이 문제를 해결하면서 공부한 내용을 정리하고자 한다.
플랫폼 마다 다른 키보드 열림 시 페이지 처리방식
안드로이드와 ios는 가상키보드가 열릴 때 페이지를 차지하는 방식이 다른데,
- 안드로이드는 키보드가 열릴 때 viewport를 키보드를 제외한 영역만큼의 height를 설정해준다.
- ios는 viewport가 아닌 document를 키보드 영역 만큼 밀어버린다. → fixed 요소가 키보드 아래에 갇히는 문제 발생
여러가지 구현 방법이 있겠지만, 나는 키보드 높이와 키보드 유무에 따라 하단 요소의 위치를 변경해주는 식으로 해결하고자 했다.
(추후 서술하지만, 여기에 문제점도 존재한다 🥲)
VisualViewport API 이용하기
visualViewport는 사용자가 실제로 볼 수 있는 화면 영역에 대한 정보를 제공한다. 이 API를 활용하면 키보드가 열렸을 때 viewport의 시각적 변화를 감지할 수 있다.

1-1 키보드 상태 감지하기
키보드가 열렸는지 감지하기 위해 visualViewport.height와 window.innerHeight를 비교한다.
window.innerHeight
: 페이지의 전체 뷰포트 높이를 나타냅니다. 키보드가 열리더라도 이 값은 변경되지 않으며, 화면 전체의 논리적 크기를 기준으로 합니다.visualViewport.height
: 사용자가 실제로 볼 수 있는 화면의 높이를 나타냅니다. 키보드가 열리면 키보드가 가시 영역 일부를 차지하므로 이 값은 키보드 높이만큼 감소합니다.
두 값의 차이가 발생하면 키보드가 열렸다고 판단 할 수 있다.
useEffect(() => {
const visualViewport = window.visualViewport;
const handleResize = () => {
if (!visualViewport) return;
const viewportHeight = visualViewport.height;
const isKeyboardVisible = viewportHeight < window.innerHeight;
setKeyboardOpen(isKeyboardVisible);
};
if (visualViewport) {
visualViewport.addEventListener("resize", handleResize);
}
return () => {
if (visualViewport) {
visualViewport.removeEventListener("resize", handleResize);
}
};
}, []);
+) 24.11.21 추가
fixed + bottom: 0 요소 내부에 포커스 가능한 요소가 있을 때, 키보드 열림 상태를 감지하지 못하는 이슈
위의 코드로 구현 시 키보드가 열릴 때도 isKeyboardOn
플래그가 false로 유지되는 문제가 있었다.
문제 상황을 따라가자면,
- 바텀시트같은 fixed 요소 내부에 focusable한 요소가 포함되어 있고, 포커스 시 브라우저가 화면을 스크롤함
- 이때
window.innerHeight
와window.visualViewport.height
가 동일하게 측정되어, isKeyboardOn가 false로 설정됨
문제 원인은 (Safari)브라우저의 기본동작 때문이였다.
Safari는 입력 필드가 화면에 노출되도록 자동으로 스크롤한다.
기존 로직에서는window.innerHeight
와 window.visualViewport.height
를 비교해 키보드 열림 여부를 판단했다.
하지만 window.innerHeight
는 스크롤을 포함하지 않은 현재 가시 영역의 높이만 반환하므로, visualViewport.height
와 동일한 값을 가지게 되는 것이다.
window.innerHeight
에 스크롤 높이(window.scrollY)를 합산하여 스크롤을 포함한 전체 화면 높이를 계산하도록 수정하면 된다!
const isKeyboardVisible = viewportHeight < window.innerHeight + window.scrollY;
1-2 키보드 높이를 활용해 하단 요소 이동시키기
키보드가 열리면 전체 viewport 높이와 시각적 viewport의 차이를 계산하여 키보드의 높이를 구한다.
이 값을 이용해 하단 요소를 키보드 위로 이동시킨다.
const fixedElStyle: CSSProperties = {
position: "fixed",
bottom: isKeyboardOpen ? `calc(100% - ${viewportHeight}px)` : "0",
left: 0,
right: 0,
transition: isIOS
? `bottom ${KEYBOARD_ANIMATION_DURATION}ms cubic-bezier(${KEYBOARD_ANIMATION_BEZIER.join(",")}), transform 0.1s ease-out`
: "bottom 0.1s ease-out",
opacity: isKeyboardOpen ? 1 : 0,
pointerEvents: state.isKeyboardOpen ? "auto" : "none",
};
isKeyboardOpen 상태를 기반으로 하단 요소의 bottom 값을 동적으로 설정했다.

짜잔 최종 결과물이다!
이제 키보드가 열리면 하단 고정 요소가 자연스럽게 키보드 위로 이동한다.
그리고 남아있는 문제점
현재 구현 방식은 실제 viewport가 줄어드는 것이 아니라 요소의 위치를 조정하는 방식이다.
ios 키보드가 열릴 때 애니메이션과 위치가 이동할 때의 애니메이션 싱크가 완벽히 일치하지 않아 어색하게 느껴지는 감이 있다.

그리고 스크롤 할 때도....ㅜㅜ
지금드는 생각으론, 스크롤 시 input blur 처리하는건 어떨까 싶기도 하다 🤔