현상
iOS Safari에서 소프트웨어 키보드가 가시적일 때, viewport 메커니즘이 깨져서 position:fixed 요소와 viewport 계산이 올바르게 동작하지 않습니다.
재현 예시
- iPhone 또는 iPad의 Safari 브라우저를 엽니다.
- 페이지 로드합니다. position:fixed 요소가 올바르게 표시됩니다.
- contenteditable 요소에 탭하여 키보드를 나타냅니다.
- ❌ position:fixed 요소가 화면 밖으로 이동하거나 틀린 위치에 감습니다.
- window.innerHeight 값이 이상하게 변합니다.
관찰된 동작
- position:fixed 깨짐: 키보드가 나타나면 fixed 요소의 위치가 계산이 틀려짐
- viewport 높이 오차: window.innerHeight가 올바르지 않은 값을 반환 (키보드 영역을 제대로 반영 안함)
- UI 사용 불가: 래딩 툴바나 메뉴가 제대로 작동하지 않아 사용자가 편집 불가
- 키보드 숨김 후 복구 안됨: 키보드를 숨겨도 viewport 높이가 복구되지 않음
- iOS Safari 특유: iPhone/iPad Safari에서만 심하게 발생
예상 동작
- position:fixed 요소가 키보드 상태와 상관없이 항상 올바른 위치에 있어야 함
- viewport 관련 CSS 속성이 올바르게 계산되어야 함
- 사용자는 키보드가 있든 없든 UI를 정상적으로 사용할 수 있어야 함
참고사항 및 가능한 해결 방향
- resize 이벤트 모니터링: 키보드 표시/숨김 감지
- iOS Visual Viewport API: experimental이지만 더 정확한 viewport 정보 제공 가능
- CSS viewport-fit 사용:
<meta name="viewport" content="viewport-fit=cover">시도 - position: absolute로 대안: position:fixed 대신 position: absolute 사용
- 사용자 제어: “완료” 버튼 추가하여 사용자가 직접 키보드 조작
- 안전 영역 확보: 래딩 UI에 충분한 여백(margin/padding) 추가
- transform 사용: position 대신 transform: translate 사용 시도 (일부 경우 도움)
코드 예시
const editor = document.querySelector('[contenteditable]');
const fixedElement = document.querySelector('[style*="position: fixed"]');
let initialHeight = window.innerHeight;
let keyboardVisible = false;
// 키보드 감지 (iOS 특유)
window.addEventListener('resize', () => {
const currentHeight = window.innerHeight;
const heightDiff = initialHeight - currentHeight;
if (heightDiff > 150) {
// 키보드 나타남
keyboardVisible = true;
console.log('키보드 가시:', currentHeight, '원래:', initialHeight);
// UI 위치 조정
adjustUIForKeyboard(true);
} else if (heightDiff < 100 && keyboardVisible) {
// 키보드 숨김
keyboardVisible = false;
console.log('키보드 숨김:', currentHeight);
// UI 위치 복구
adjustUIForKeyboard(false);
}
initialHeight = currentHeight;
});
// UI 조정 함수
function adjustUIForKeyboard(isKeyboardVisible) {
if (isKeyboardVisible) {
// 키보드가 있을 때: UI를 위로 이동
// position:fixed 요소를 안전한 위치로 조정
if (fixedElement) {
fixedElement.style.transform = 'translateY(0)';
// 또는 position: absolute로 변경 시도
}
} else {
// 키보드가 없을 때: UI 원위치 복구
if (fixedElement) {
fixedElement.style.transform = 'none';
}
}
}
// Visual Viewport API (iOS 지원 시)
if (window.visualViewport) {
console.log('Visual Viewport:', window.visualViewport);
}
// 완료 버튼 추가 (사용자가 키보드 직접 조작 가능하게)
const doneButton = document.createElement('button');
doneButton.textContent = '완료';
doneButton.style.cssText = 'margin-top: 10px; padding: 10px 20px; background: #007AFF; color: white; border: none; border-radius: 4px;';
doneButton.addEventListener('click', () => {
editor.blur(); // 키보드 숨김
setTimeout(() => {
// 키보드가 숨길 때까지 기다린 후 포커스 복구
editor.focus();
}, 300);
});
document.body.appendChild(doneButton);
/* CSS 해결책 시도 */
.fixed-element {
/* 대안 1: position: absolute로 변경 */
/* position: absolute;
bottom: 10vh;
*/
/* 대안 2: transform 사용 */
transform: translate3d(0, 0, 0);
/* 안전 영역 확보 */
margin-bottom: 50px; /* 래딩 UI를 위한 여백 */
}
[contenteditable] {
/* 충분한 최소 높이 확보 */
min-height: 200px;
padding-bottom: 300px; /* 키보드 공간 확보 */
}
/* viewport meta 태그 */
/* <meta name="viewport" content="viewport-fit=cover"> */