아이폰 fixed 스크롤 버그 - aipon fixed seukeulol beogeu

아이폰 fixed 스크롤 버그 - aipon fixed seukeulol beogeu

2021년 9월 20일에 iOS15가 업데이트 되었습니다. 이번 릴리즈에는 Safari 브라우저에서 아주 큰 변화가 있었어요. 바로 브라우저 내의 주소창이 아래로 이동이 되었다는점!

채널톡은 위처럼 고객이 메시지를 입력할 수 있는 입력란이 하단에 있어요. 항상 고객에게 최고의 채팅경험을 제공하기 위해 끊임없이 고민하는 채널팀에게는 키보드를 열었을때 입력창이 가려지는 문제는 너무나도 치명적이었죠. 뿐만 아니라, 채널톡은 유저가 다양한 환경에서 사용하는 만큼 여러 고민들이 생겼어요.

iOS15 릴리즈를 위한 대응 작업을 하며 크로스브라우징과 유저에게 어떻게 유려한 채팅경험을 줄수 있을지에 대해 고민한 과정과 결과들을 공유하려고 해요.

iOS 브라우저의 구조

iOS 기기의 브라우저는 Android 기기의 브라우저와는 다른 특이한 구조를 가지고 있어요. Android OS는 키보드를 열 때 screen에서 키보드를 제외한 영역 만큼의 높이를 viewport로 조절해주게 됩니다. 그래서 키보드를 열어도 document가 표시되는 영역이 screen에서 키보드 높이를 제외한 영역에 정상적으로 표시해 줍니다. 하지만 iOS는 키보드를 열더라도 viewport를 조절해주지 않습니다. 키보드를 열면 기존의 document를 키보드 높이 만큼 밀어 올릴 뿐이에요.(document는 흔히 아는 document객체를 의미합니다.)

위 영상에서 키보드를 열기전에는 document가 존재할 수 있는 영역은 화면의 높이 만큼입니다. 하지만, 키보드를 열면 document를 키보드 높이만큼 그대로 밀어 올리고 키보드 뒤쪽에는 가상의 영역을 생성해 줍니다. 따라서 document가 존재할 수 있는 영역은 아래와 같습니다.

  • 키보드를 열기전 : 화면의 높이
  • 키보드를 열었을 때 : 화면의 높이 + 키보드의 높이

주의할 점은 document가 존재할 수 있는 영역이고 document자체의 높이가 변하는 것은 아닙니다. document의 높이는 키보드 활성화 여부 상관없이 동일합니다. 또한, 키보드를 연다음 스크롤하면 의도치 않게 동작하는게 한가지 있습니다. 위 영상의 입력창은 position이 fixed입니다. 따라서, Android 기기에서는 키보드를 연 다음 스크롤해도 입력창이 키보드 밑으로 없어지지 않고 키보드 위에 잘 붙어있습니다. 하지만 iOS에서는 스크롤시 입력창이 키보드 뒤쪽으로 가려지게 됩니다.

이는 마치 iOS에서는 position: fixed가 동작하지 않는다 처럼 생각할수도 있습니다. 실제로도 이 이슈에 대해 stack overflow 에서도 수많은 글들을 찾아볼 수 있습니다. 하지만, iOS에서는 position: fixed가 동작하지 않는것은 아니고 정확히는 position: fixed가 동작하지 않는 것처럼 보인다 가 맞는 표현 같습니다.

일반적으로 페이지에 손가락을 대고 움직이면 스크롤이 됩니다. 이는 정확히는 html태그가 스크롤 된다고 생각하면 됩니다(호환모드에서는 body태그가 스크롤 됩니다. 표준모드, 호환모드에 대해서는 여기를 참고해주세요). 위에서 iOS는 키보드를 열었을 때 키보드 뒤쪽에는 가상의 영역을 생성해 준다고 했습니다. iOS에서는 키보드를 연 후에 스크롤을 하게되면 html태그가 스크롤되기 전에 먼저 document가 키보드 뒤쪽의 가상영역으로 스크롤 됩니다. 그렇게 스크롤이 되다가 document가 키보드 뒤쪽의 가상영역의 하단에 닿게 되면 그때부터 내부의 html태그가 스크롤이 됩니다. 따라서, position: fixed이더라도 이는 document내에서 fixed일뿐 스크롤시 document자체가 키보드 뒤쪽의 가상영역 뒤로 숨겨지게 되어 마치 position: fixed가 동작하지 않는 것처럼 보이게 됩니다.

키보드가 열린 상태에서 스크롤시 가상영역 뒤쪽을 먼저 채우는 게 아닌 정말 document내의 element가 스크롤 되도록 하는 방법이 있습니다. 이에 대해서는 아래에서 다시 다루겠습니다.

채널톡은 어떤 구조를 선택했는지

채널톡은 크게 두가지 구조를 놓고 고민했어요.

  1. 페이지의 높이가 screen의 높이(height: 100%)로 고정이 되어있고 내부에서 스크롤이 되는구조
  2. 페이지의 높이를 고정하지 않고 내부 element가 늘어남에 따라 전체 document 높이도 증가하는 구조

위 두가지 구조는 구현상, 동작상 큰 차이가 있습니다. 채널톡은 위 두가지 구조중 많은 고민끝에 2번의 구조를 선택했습니다.

각각 장단점이 있었는데 1번의 경우는 채널톡의 경쟁사 대부분에서 사용하는 구조입니다. 이 구조의 가장큰 장점은 고객사 화면에서 곧바로 채팅화면을 띄울수 있다는 장점이 있습니다. 하지만 모바일기기에서 브라우저 사용시 페이지를 스크롤 할 때 방향에 따라 주소창이 활성화 / 비활성화 되는것을 많이 보셨을것 같아요. 하지만 1번의 구조에서는 document가 스크롤 되는게 아닌 내부의 element가 스크롤 되기 때문에 브라우저의 스크롤 경험은 살리지 못한다는 단점이 있어요

채널톡은 스크롤 경험을 살리고자 2번의 구조를 선택했습니다. 따라서, 지금처럼 모바일에서는 채팅방 클릭시 채팅창이 새탭으로 열리는 구조로 방향이 설정되었습니다.

어떤 이슈가 있었는지

1. document가 키보드 뒤쪽으로 스크롤 되는 문제

위 iOS브라우저의 구조에서 언급했던 것처럼 iOS브라우저에서는 키보드를 활성화 시킨 후 스크롤시 document가 먼저 키보드의 가상영역 뒤로 감춰지는 문제가 있었습니다. 하지만, 아래와 같은 조건이 채워지면 스크롤시 가상영역 뒤쪽을 먼저 채우는 게 아닌 정말 document내의 element가 스크롤 되도록 할 수 있습니다.

  • 터치하여 움직이는 영역이 scrollable하다(overflow: auto | scroll)
  • scrollable한 영역이 실제로 스크롤이 되어야 한다(내부에 height가 더 높은 다른 element가 존재해야한다)

위 조건이 채워지면 키보드가 활성화 된 상태에서 스크롤을 해도 document가 키보드 뒤의 가상영역으로 내려가지 않고 document 내부가 스크롤 됩니다.

이를 위해 아래와 같이 구현했어요.

<div id="wrapper">  
  <div id="content">...</div>
  <div id="make-scrollable"></div>
</div>

/* css */
#wrapper {
  position: relative;
  width: 100%;
  height: 200px;
  overflow-y: auto;
}

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

#make-scrollable {
  position: absolute;
  left: 0;
  width: 1px;
  height: calc(100% + 1px); // height를 100%보다 1px높게 잡아 실제로 scroll이 되도록 만듭니다.
}

2. 키보드 변경시 입력창이 가려지는 문제

모바일에서는 한국어, 영어, 일본어, 이모티콘 등 입력내용에 따른 다양한 키보드가 존재합니다. iOS에서는 각각의 키보드가 높이가 다릅니다. 이런 특징때문에 iOS브라우저에서 높이가 더 높은 키보드로 변경시 입력창이 가려지게 되었어요.

채널톡은 한국, 일본, 미국 등 글로벌로 서비스를 제공하고 있습니다. 이렇게 사용자가 다양한 언어의 키보드를 사용하게 되는 환경에서 입력창이 가려지는 문제는 채팅경험을 저하시키는 큰 이슈였어요. 이 문제를 해결하기 위해 키보드 활성화시 viewport의 높이를 계산해서 document위치를 강제이동시키거나 키보드 활성화 여부에 상관없이 document의 height는 동일한점을 이용해 document의 위치를 계산하는 등 다양한 시도를 했지만, iOS기기의 키보드의 높이를 하드코딩 방식으로 구현한거라 관리, 확장성 측면에서 더 좋은 방향을 고민하게 되었습니다. 많은 리서치 끝에 visualViewport의 resize이벤트를 사용하는 방식으로 구현했습니다.

let prevVisualViewport = 0

function handleVisualViewportResize() {  
  const currentVisualViewport = window.visualViewport.height

  if (
    prevVisualViewport - 30 > currentVisualViewport &&
    prevVisualViewport - 100 < currentVisualViewport
  ) {
    const scrollHeight = window.document.scrollingElement.scrollHeight
    const scrollTop = scrollHeight - window.visualViewport.height

    window.scrollTo(0, scrollTop) // 입력창이 키보드에 가려지지 않도록 조절
  }

  prevVisualViewport = window.visualViewport.height
}

window.visualViewport.onresize = handleVisualViewportResize  

위 코드와 같이 window.visualViewport의 onresize에 handler를 등록하면 visualViewport가 변경될 때마다 handler가 호출됩니다. 이 handler안에서 입력창이 키보드에 가려지지 않도록 처리했습니다.

키보드 변경에 따라 입력창이 가려지지 않도록 개선한 경험은 현재 타사의 채팅서비스중 유연하게 대응한 곳이 없다는 점에서 굉장히 자부심을 갖고 있습니다.

3. 일본어 자동완성, 한국어 버퍼 문제

일반적인 텍스트 입력폼은 텍스트 입력후 전송시 입력창에서 focus가 해제되며 입력과정이 종료됩니다. 일반적으로 댓글입력을 생각해보면 될 것 같습니다. 하지만 채널톡의 경우 유려한 채팅경험을 위해선 메시지 전송후에도 입력창이 focus가 유지되어야 합니다. 이렇게, 입력창에 focus를 유지시키고자 할때 iOS에서 메시지 입력시 아래의 두가지 문제가 있었습니다.

  • 한국어 입력 시 마지막 글자가 받침이 없는 글자일 경우 버퍼에 남게 되어, 다음 메시지 입력 시 의도치 않게 입력 됨
  • 일본어로 메시지 입력후 전송시 자동완성으로 인해 버튼을 두번 눌러야 전송됨

첫번째 영상을 보면 '안녕하세요' 를 입력후 그다음 '채' 한글자를 입력했는데 '요채' 가 입력된 것을 볼 수 있습니다. 두번째 영상은 일본어로 텍스트 입력후 바로 전송버튼을 눌러도 전송이 되지 않고 키보드 오른쪽 하단의 버튼을 클릭후 전송버튼을 클릭해야 전송이 됩니다.

위 이슈들을 해결하는 방법으로 검색을 해보면 hidden input에 대한 내용들을 많이 찾아볼 수 있습니다. hidden input은 실제 입력창 외에 visibility: hidden 인 보이지 않는 input을 하나 더 생성하여 전송버튼 클릭시 focus를 hidden input으로 이동시킨후 다시 기존 입력창으로 focus를 이동시키는 방법입니다.

function handleClickSendButton() {  
  const input = document.getElementById('input')
  const hiddenInput = document.getElementById('hidden-input')

  hiddenInput.focus()
  input.focus()

  sendMessage(message)
}

<div>  
  <input id="input" />
  <input id="hidden-input" />
</div>


/* css */
#hidden-input {
  visibility: hidden;
}

위와같이 구현했을때 다른 input으로 포커스가 이동되면서 버퍼에 남아있던 마지막 글자가 사라지며 일본어 자동완성도 풀리게 되어 이슈를 해결할 수 있습니다. 하지만, hidden input을 사용했을때 iOS에서는 키보드내의 상단메뉴의 화살표를 클릭하여 각 input의 focus를 이동시킬수 있습니다.

이런 특징 때문에 위 영상처럼 input의 포커스를 이동시킬때 스크롤이 이동되어 입력창이 가려지는 문제가 있었어요. iOS에서는 input에 포커스가 가능한지 여부를 파악해 위 영상과 같은 동작이 일어나게 됩니다. 그렇기 때문에 hidden input에 pointer-events: none 스타일 속성을 추가시켜 포커스가 되지 않도록 하여 해결이 가능했습니다.

크로스 브라우징

채널톡은 사용자가 다양한 환경에서 사용하는만큼 크로스브라우징이 굉장히 중요합니다. 이번 업데이트는 iOS15 대응을 위해 시작했지만 iOS환경 외에도 Android 기기, 인앱브라우저 등에서도 미려한 채팅경험을 제공하기 위해 다양한 시도를 했습니다.

1. Android, iOS 키보드 활성화 여부

모바일에서 키보드가 활성화된 상태이면 document가 보이는 화면의 영역도 줄어들며 스크롤 동작도 많은 부분이 달라집니다.

Android와 iOS는 키보드 활성화 여부를 판단하는 기준이 다릅니다. iOS는 input에 focus가 되었는지 여부로 키보드가 활성화 되었는지 알 수 있습니다. 하지만, Android는 input의 focus 여부가 키보드 활성화 여부와 일치하지 않습니다. Android에서는 window의 resize이벤트 핸들러를 등록하여 키보드 활성화 여부를 알 수 있습니다.

let isFocus = false  
const initialClientHeight = window.innerHeight

function handleFocus() {  
  isFocus = true
}

function handleBlur() {  
  isFocus = false
}

function handleResize() {  
  if (window.visualViewport.height < initialClientHeight) {
    isFocus = true
  } else {
    isFocus = false
  }
}

window.addEventListener('resize', handleResize)

<input  
  onFocus={handleFocus}
  onBlur={handleBlur}
/>

2. backdrop-filter

채널톡의 이번 업데이트에서 또 한가지 눈에 띄는 점은 입력창에 backdrop-filter가 적용되었어요! 스크롤할때 입력창을 보시면 뒤쪽이 비치는 것을 보실 수 있습니다.

backdrop-filter속성은 opacity가 존재하는 엘리먼트에만 적용이 가능합니다. 하지만, backdrop-filter는 Firefox, IE등 지원하지 않는 브라우저가 존재하기 때문에 이런곳에서는 opacity만 보여지는 이슈가 있었어요.

아이폰 fixed 스크롤 버그 - aipon fixed seukeulol beogeu

위 이미지를 보시면 입력창이 opacity만 적용되어 뒤쪽의 내용이 비추어 보이는 문제가 발생합니다. 각 브라우저별로 지원하는 css속성이 다르다보니 발생한 이슈인데 css에는 기본적으로 해당 css를 지원하는지 여부를 검사할 수 있는 인터페이스가 있습니다. 채널팀은 다음과 같은 방법으로 이런 이슈를 해결했어요

@supports (backdrop-filter: blur()) or (-webkit-backdrop-filter: blur()) {
  background: rgba(252, 252, 252, 0.8);
  backdrop-filter: blur(60px);
}

남은 과제는?

이번에 iOS15 대응 및 개선작업을 진행하며 가장 많이 고민했던 부분은 '크로스 브라우징' 이었어요. 채널톡은 수많은 사용자가 다양한 환경에서 사용하는 만큼 어떤 기기, 어떤 브라우저든 원활하게 채팅을 할 수 있도록 하는 게 무척 중요합니다.

iOS15 대응작업을 하면서 다양한 기기, 환경에서 수많은 테스트를 하며 Android 기기에서 버그가 있어 수정하면 iOS기기에서 사이드 이펙트로 인해 버그가 발생하는 경우가 많았습니다.

앞으로 채널톡에서 풀어야 할 과제는 다양한 기기, 브라우저 별로 각각의 코드가 영향을 받지 않도록 독립화, 모듈화 하여 크로스 브라우징에 최적화 할 수 있는 구조로 개선을 진행중이며 장기적으로는 e2e테스트를 적극적으로 활성화하여 여러 환경에서의 테스트에 드는 리소스를 줄이는 중요한 과제가 남아있습니다.

끝으로

이번 채널톡의 업데이트는 애플의 iOS15 Safari 브라우저 주소창이 하단으로 이동하게 된 이슈에서 시작하여 Safari 뿐만 아니라 Android, 데스크탑 등 다양한 환경에서 사용자에게 최고의 채팅경험을 제공하고자 다양한 시도를 했던 프로젝트였어요. 많은 시간과 노력이 들어갔지만 7천만 유저에게 노출되는 채널톡의 채팅경험이 업데이트 이전보다 월등히 좋아진 것이 느껴지는 부분에서 정말 뿌듯하고 만족스러운 프로젝트였죠.

채널톡은 최고의 채팅경험을 제공하기 위해 사용자가 사소하다고 느낄수 있는 부분 하나하나도 놓치지 않고 수많은 리서치를 통해 개선해 나가고 있습니다.


최고의 채팅경험을 만들어 나가는 채널톡 웹팀!
함께 하고 싶다면, 지금 바로 지원해주세요 🙌

아이폰 fixed 스크롤 버그 - aipon fixed seukeulol beogeu