[Vue3] Android 키보드 스크롤 관련 문제

Heegun
더컴퍼스 기술 블로그

--

Android 환경에서 채팅 기능을 구현하기 위한 과정에서 position: fixed 로 하단에 고정해둔 Input이 스크롤 시 같이 올라오지 않는 현상이 발생했다.

우선 코드부터 보자.

<template>
<div id="wrapper" >
<div id="content">
<div v-for='i in 120' >
{{ i }}
</div>
</div>
<div id="scrollable"></div>
<input id='chatInput' placeholder='Chatting Input'/>
</div>
</template>

<style scoped>

#wrapper {
height: 100%;
width: 100%;
position: relative;
background-color: #008000;
margin-bottom: 48px;
overflow-y: auto;
}

#content {
height: auto;
width: 100%;
}

#content div{
background-color: #E5DC8C;
}

#scrollable {
position: absolute;
height: calc(100% + 1px);
width: 1px;
left: 0;
}

#chatInput {
width: 100%;
height: 48px;
position: fixed;
bottom: 0;
left: 0;
background-color: #6C7680;
}
#chatInput::placeholder{
color: #ffffff;
}

</style>

스크롤 기능 구현은 아래 포스트를 참고하였다.

이제 발생한 문제를 보자.

첫 번째 화면에서 Chatting input 을 focus 하여 키보드를 열고 상단으로 스크롤 하면 Chatting input 을 두고 올라오는 현상이 발생한 것이다. 문제는 여기서 그치지 않는다.

위의 화면은 삼성 인터넷 브라우저에서 실행했을 때의 현상이다. Chatting input 이 정상적으로 같이 올라오는 것을 볼 수 있다. 같은 코드인데 참으로 이상한 일이다. 이는 각 브라우저마다 모바일에서 키보드가 열렸을 때 document 와 viweport 를 처리하는 방법이 다르기 때문에 발생하는 문제이다. ( 화면에 ~ms 가 쓰있는 chip 은 Nuxt 프로젝트를 모바일에서 실행했을 때 생성되는 dev tool 이다. 무시하자.)

viewport

그렇다면 이 viewport 가 무엇인지부터 알아보자. viewport 는 사용자에게 보는 화면의 정보에 대한 객체이다. 이는 사용자의 스크롤 등의 행동에 따라 크기가 달라진다. 당연히 키보드의 유무 상태에 따라서도 달라진다. 말 그대로 사용자에게 보이는 화면의 크기에 대한 정보를 가지기 때문이다. 따라서 document 객체의 정보와는 다른 그것을 가진다.

Chrome 브라우저에서는 키보드가 열렸을 때 브라우저 창의 크기를 viewport의 크기로 줄이는 것이 아닌, document 문서 자체를 키보드의 높이만큼 위로 밀어내서 키보드의 공간을 확보한다. 그런데 이 input 에 부여된 속성인 position: fixed 는 viewport 가 아닌 document 객체를 기준으로 위치를 지정해 준다. 그러므로 상단으로 스크롤 하면 document 객체를 기준으로 하단에 고정되어 있던 input 이 밑에 남아있는 현상이 발생하는 것이다.

그와는 다르게 삼성 인터넷 브라우저는 키보드가 열리게 되면 document 객체의 높이 자체를 viewport 만큼으로 줄여버린다. 따라서 document 기준으로 하단에 고정되어 있는 Chatting input 이 스크롤이 이동함에 따라서 함께 올라오게 되는 것이다.

Chrome 브라우저

그림이 이해에 도움이 되기를 바란다. 노란색으로 둘러싸인 부분이 document 객체의 크기이다. 빨간색은 Chatting input 으로 document 객체를 기준으로 하단에 고정되어 있으며 초록색과 빨간색을 합친 것이 viewport 영역을 표시한 것이다.

첫 번째 그림이 초기 화면이다. 이 상태에서 input 을 터치하면 키보드가 열려 두 번째 화면이 된다. 이때 Chrome 브라우저는 document 객체 자체를 밀어내며 viewport 영역의 크기는 줄여준다. 여기서 상단으로 스크롤 하게 되면 wrapper div 영역이 아니라 외부인 document 객체를 스크롤 하게 되어 document 객체의 윗부분을 보게 되는 것이다. 따라서 document 기준으로 하단에 고정되어 있던 Chatting input 은 밑에 남아있게 된다.

반면에,

삼성 인터넷 브라우저

삼성 인터넷의 경우 document 객체의 높이 자체를 viewport 의 높이 만큼으로 줄여서 키보드의 공간을 확보한다. 따라서 스크롤이 이동함에 따라 Chatting input 이 제대로 하단에 붙어서 따라오게 되는 것이다.

직접 확인해보자.

<template>
<div id="wrapper" >
<div id="content">
<div v-for='i in 120' @click='contentClicked'>
{{ i }}
</div>
</div>
<div id="scrollable"></div>
<input id='chatInput' placeholder='Chatting Input'/>
</div>
</template>

<script setup lang="ts">

const contentClicked = () => {
alert(document.documentElement.clientHeight)
alert(window.visualViewport.height)
}

</script>

위의 함수를 통해 순서대로 키보드가 열려있을 때의 document 객체의 높이, viewport 높이를 확인할 수 있다. 각 브라우저에서 실행해 보자.

Chrome 브라우저

삼성 인터넷 브라우저

각 브라우저끼리 viewport 의 크기가 다른 것은 브라우저의 툴바 등에 의한 것이므로 무시해도 된다. 주목할 만한 것은 Chrome 브라우저의 경우 키보드가 열렸을 때에도 document 객체의 높이가 viewport 의 높이 변화와 무관하게 그대로인 것을 볼 수 있다. 반면 삼성 인터넷 브라우저의 경우 단위만 다를 뿐 document 객체의 높이가 viewport 의 높이와 같아지게 된다.

해결 방안

이 문제를 해결하기 위해 가장 먼저 떠올린 방법은 document 객체의 높이를 viewport 의 높이만큼 직접 줄이는 것이다. 하지만 이 방법은 불가능하다. document 객체의 속성 자체를 수정하는 것은 불가능하기 때문이다.

<template>
<div id="wrapper" >
<div id="content">
<div v-for='i in 120' @click='contentClicked'>
{{ i }}
</div>
</div>
<div id="scrollable"></div>
<input id='chatInput' placeholder='Chatting Input' @click='changeHeight'/>
</div>
</template>

<script setup lang="ts">
const contentClicked = () => {
alert(document.documentElement.clientHeight)
}

const changeHeight = () => {
let documentHeight = document.documentElement.clientHeight; // document 객체의 높이
let viewportHeight = window.visualViewport.height; // viewport 의 높이
let keyboardHeight = documentHeight - viewportHeight + 1; // 키보드의 높이

document.documentElement.clientHeight = keyboardHeight;
document.documentElement.style.height = keyboardHeight;
}

</script>

키보드가 열렸을 때의 document 객체의 높이에서 viewport 의 높이를 빼서 키보드의 높이를 계산했다. +1 을 한 것은 단위의 문제 때문에 생기는 오류를 방지하기 위해서이다.

실제로 위의 코드처럼 document 객체의 clientHeight 에 직접 키보드의 높이 값을 넣거나, style.height 속성에 값을 넣어도 제대로 반영되지 않는다.

document 객체의 높이가 그대로이다 ..

그러므로 우리는 다른 방법을 찾아야 한다. Chatting input 이 키보드 위에 붙어있기를 원한다면 input 을 키보드의 높이만큼 끌어올리면 될 것이다. 위의 코드에서 changeHeight 를 아래와 같이 바꿔보자.


const changeHeight = () => {
let documentHeight = document.documentElement.clientHeight; // document 객체의 높이
let viewportHeight = window.visualViewport.height; // viewport 의 높이
let keyboardHeight = documentHeight - viewportHeight + 1; // 키보드의 높이

const chatInput = document.getElementById('chatInput')
chatInput.style.bottom = keyboardHeight + "px"
}

여전히 해결되지 않는다 .. 그렇지만 거의 다 왔다. 위의 단계에서 해결이 안 되는 이유는 viewport 가 변할 때마다 키보드의 높이를 계산하여 input 의 bottom 값을 바꿔줘야 하기 때문이다.

onMounted(() => {
window.visualViewport.onresize = changeHeight;
})

위의 코드를 추가하면 스크롤이 정상적으로 작동하는 것을 확인할 수 있다 ! onMounted 훅을 이유는 window 객체에 접근하기 위해서이다. 이에 대한 자세한 설명은 아래의 글을 참고하면 좋을 것 같다.

--

--