Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

모바일 사파리에서 스크롤 구현과 관련된 문제

HTML5가 나오고 나서 웹에도 엄청난 변화가 일어났지만 초반에 열심히 쫓아가다가 너무 많이 쏟아져 나와서 한참동안은 너무 깊게 파지는 않고 있었고 모바일은 딱히 만질일도 없었기 때문에 기본적인 수준 이상으로는 크게 관심을 두지 않았었다. 지금은 프론트앤드쪽 작업을 하고 있으므로 모바일도 좀 알아야 되겠다 싶어서 모바일웹쪽을 만져보고 있는 중이다. 개인 프로젝트에서 모바일 웹을 작업하고 있는데 스크롤을 구현하는게 상당히 골치가 아팠다. 스크롤은 어디나 들어가는 아주 일반적인 기능이라고 생각하고 있었음에도 구현하는데 여러가지 어려움이 상당히 많았다.

모바일에서 스크롤 관련 라이브러리로 가장 유명한 것은 iScroll이다. 현재는 4버전이 최신 버전이고 iScroll 5가 베타상태에 있다. 처음에는 iScroll로 작업을 했는데 처음 사용해봐서 제대로 구현못한 건지 모르겠지만 아주 깔끔하게 안되는 부분이 좀 있었고 안드로이드 기기로 넘어가니 퍼포먼스가 너무 안나왔다. 그래서 OS 탑재된 네이티브 스크롤을 직접 이용하기로 하고 iScroll을 제거하고 직접 구현하고 있는데 그 가운데 스크롤에 관련된 내용을 좀 정리해야 좀더 다양한 기능 구현 및 최적화를 할 수 있을것 같아서 정리를 한다.(이미 다 아는 내용인데 나만 이제 확인한 것 같긴 하지만)

일단 내가 가진게 iOS라서 iOS에 맞추어서 작업을 하고 있다. 안드로이드에서도 동작하리라고 생각하지만 많은 테스트를 해보진 않았다. 예제는 Angular.js를 사용했는데 이벤트나 좌표를 추적해서 보여주기 위한 것이므로 스크롤과는 상관이 없다. 그리고 스크롤을 설명하는 예시일 뿐이므로 퍼포먼스가 많이 고려되어 있지는 않다.(누가 잘아시는 분이 설명을 해주시면 감사.)

스크롤

스크롤 영역으로 지정하는 것은 딱히 모바일 웹에 특화된 것은 아니다. 스크롤을 사용할 곳의 크기를 지정하고 CSS 속성에서 overflow: auto;로 지정하면 된다. 이러면 해당 영역보다 작을때는 상관없지만 해당 영역보다 내용이 많아지면 자동으로 스크롤이 동작하게 된다. 가로나 세로만 하고 싶다면 overflow-x: auto;, overflow-y: auto; 같은 식으로 작성하면 된다. 다음 예제를 보자.(스크롤은 스샷을 찍어서 설명하기가 어렵고 또 동영상을 찍기고 쉽지 않아서 실행해 볼 수 있는 예제를 만들었다. 이벤트가 다르므로 데스크탑에서는 제대로 동작하지 않을 수 있으므로 모바일장비에서 테스트해봐야 한다.)

실행가능한 예제 링크

각 동작을 한눈에 볼 수 있도록 예제를 구성했다.(폭이 좁아서 블로그내에서는 예제를 실행해보기 어려우므로 모바일에서 띄워보는게 확인하기 좋다.) 상단에는 좌표관련 정보를 표시하고 좌측에는 예제의 스크롤 영역이 있다. 우측에는 스크롤 동작과 관련한 이벤트가 출력되도록 했다. 모바일에서는 마우스가 없으므로 터치이벤트를 통해서 스크롤이 동작하게 되는데 터치할 때 touchstart이벤트가 발생하고 움직이면 touchmove가 발생하고 손가락을 뗄 때 touch가 발생한다. 움직이지 않고 바로 떼면 touchmove가 발생하지 않고 바로 touchend가 발생한다. 여기서는 스크롤 영역에 대한 테스트이므로 touchmove를 통해서 스크롤이 될 때 scroll이벤트도 함께 발생한다.

좌표와 관련해서는 스크롤 영역에는 scrollTop이라는 속성이 존재한다. 가장 최상위에 있을 때는 scrollTop이 0이고 스크롤을 내릴 수록 값이 커지게 된다. 각 이벤트에 대한 좌표를 알 수 있도록 touchstart, touchmove, touchend`의 좌표를 따로 표시하도록 했다. 예제소스는 jsFiddle에서 볼 수 있으므로 따로 전체소스를 담지는 않겠다.

모멘텀(momentum) 스크롤

앞에서 본게 기본 스크롤 영역이지만 이 스크롤을 실제로는 사용할 수 없다. 모바일에서 스크롤을 할 경우에는 가속도가 먹어서 손가락을 튕기면 스크롤이 빠르게 이동하는 스크롤을 사용하고 사용자에게도 이게 당연한 동작이다. 이를 모멘텀 스크롤이라고 부르는데 앞에서 본 예제는 손가락을 떼는 순간 스크롤이 멈추기 때문에 사용하기가 아주 불편하다.

iOS 즉 모바일 사파리(웹뷰 포함)에서 모멘텀 스크롤을 사용하려면 스크롤 영역에 -webkit-overflow-scrolling: touch; CSS 속성을 지정한다. 이 스타일만 지정하면 스크롤이 자연스러운 모멘텀 스크롤로 동작하게 된다.

실행가능한 예제 링크

이제 스크롤이 아주 자연스러워 진 것을 볼 수 있다. 하지만 모멘텀 스크롤에는 몇가지 문제가 좀 있다.(이는 iOS의 버그수준이라고 생각하는데 어쨌든 문제가 있다.)

모멘텀 스크롤 중에는 scrollTop이 업데이트되지 않는다. 모멘텀 스크롤을 손가락으로 스크롤 영역을 튕겨서 동작하므로 즉 touchend이벤트가 발생한 이후에도 스크롤이 계속 이동하게 되는데 tocuchend이벤트까지는 scrollTop이 갱신되지지만 touchend이벤트가 발생한 이후에 모멘텀 스크롤가 동작하는 동안에는 scrollTop이 변하지 않고 모멘텀 스크롤이 시간이 지나서 자동으로 멈춘 순간 scroll이벤트가 발생하면서 scrollTop이 갱신된다. 그래서 현재 스크롤이 어느 위치에 있는지 찾을 수가 없고 심지어 모멘텀 스크롤이 동작하고 있다는 것 조차 알 수가 없다.(나중에 scroll이벤트가 발생하므로 모멘텀 스크롤이 동작했었구나 정도만 알 수 있다.)

모멘텀 스크롤에서 선택영역 사용하기

모멘텀 스크롤에서 선택영역을 함께 사용하지 않는다면 큰 문제는 없다. 아주 자연스럽게 스크롤을 할 수 있지만 나는 다음과 같이 하고 싶었고 어플리케이션에 따라 다르겠지만 당연한 동작이라고 생각했다.

  • 스크롤을 자연스럽게 사용할 수 있어야 한다.(이건 당연히...)
  • 리스트를 터치하면 선택했음을 알 수 있도록 색상을 변환한다.
  • 터치한 상태에서 스크롤을 하기 시작하면 선택하고자 한 것이 아니므로 선택은 취소되고 스크롤이 동작한다.
  • 스크롤도중에 클릭한 것은 선택하기 위한 것이 아니라 스크롤을 멈추고자 하는 것이므로 선택이 되지 않는다.
  • 리스트를 선택한 후에 손가락을 떼면 선택으로 동작한다.

scrollTop이 업데이트되지 않으므로 모멘텀 스크롤중의 터치도 잘못된 좌표가 인식된다. 말로 설명하려니 어려운데 스크롤 영역에 li같은 태그에 클릭 효과가 나도록 :active로 스타일을 주었을 경우 모멘텀 스크롤 중에(멈춘뒤 말고) 클릭을 해서 멈춘다면 현재 클릭한 부분이 선택영역으로 잡히지 않고 모멘텀 스크롤을 시작하기 전에 해당 위치에 있던 엘리먼트가 선택된 것으로 표시가 된다. :active 스타일이 다른 위치에 가서 먹고 실제 이벤트도 여기서 발생한다. 이는 다음 예제에서 확인해 볼 수 있다.

실행가능한 예제 링크

그래서 맨 앞에 얘기한 조건대로 클릭할 경우 리스트의 스타일을 변경해 주기 위해서 :active상태를 쓸 수 없다고 생각했다. 내가 생각한 방법은 선택에 대한 CSS 클래스를 주어서 터치이벤트에 따라 조건에 따라 클래스를 넣었다 빼었다 하는 것이다. 이렇게 하려면 지저분한 플래그가 꽤 필요하고 그래도 해결이 되지 않아서 스크롤에 대한 가속도를 측정하는 방법을 시도했다. 마지막 4개의 이벤트의 시간간격을 측정해서 모멘텀 스크롤처럼 손가락을 튕긴 것인지 아니면 그냥 스크롤을 하다가 손가락을 뗀 것인지를 판단하려고 한 것인데 일단 내 개인 프로젝트에서는 잘 동작하고 있다. 완벽하진 않지만 한 95%의 경우에서는 제대로 동작하는 것으로 보인다. 사실 이 부분도 예제로 만들려고 했지만 이건 잘 안됐다. 내가 만든것에서는 여러가지 UI가 섞여있어서인지 가속도 측정이 예제로 다시 구성하니 전혀 다르게 나와서 구성할 수가 없었다.(어째서!!!) 일단 내가 시도한 방법이 범용적으로는 쓸 수 없다고 생각해서 좀더 고민을 해봐야겠다. 정말 이 버그가 iOS 7에서는 해결이 되었으면 좋겠다. ㅠ

2013/08/18 01:51 2013/08/18 01:51