티스토리 뷰

발생일: 2012.01.10

문제:
아이폰이나 아이패드와 같은 터치 디바이스에서는 PC와 다르게 hover(마우스 롤오버) 효과가 적용되지 않는다.
'마우스' 개념이 없기 때문이다. 
실제로 모바일 웹 페이지를 작성하는 경우, 마크업 단계부터 hover 효과를 고려하지 않는다.

하지만 기존 서비스를 모바일에서도 그대로 제공해야 하는 경우라면, 서비스에 적용된 여러 가지 hover 효과 때문에 사용자가 불편을 겪을 수 있다.
예를 들어, 마우스를 오버했을 때 가이드가 표시되는 버튼을 구현했다면,
터치 디바이스에서는 한 번 클릭 시 마우스 오버 효과가 발생하고, 다시 클릭했을 때 클릭 이벤트가 발생한다.
PC에서는 한 번 클릭으로 실행 가능했던 버튼이 터치 디바이스에서는 두 번을 클릭해야 한다.

진행하고 있는 서비스에서도 비슷한 문제가 발생했고,
터치 인터페이스에서는 hover 효과 없이 한 번 클릭으로 실행될 수 있게 수정이 필요했다.

간단하게 생각해보면, 각 케이스에서 마우스 오버 이벤트를 모두 제거하면 될 것 같은데,
각 모듈마다 분기문을 추가해 이벤트를 제거할 생각을 하니 이만큼 비효율적인 것도 없다.

뭔가 엘레강스한 처리 방법이 없을까~~~


해결책:

왜 두 번 클릭해야 하나?
먼저, hover 효과가 적용된 엘리먼트에서 두 번 클릭이 필요한 이유에 대해 자세히 알아보자.

모바일 사파리에서 터치를 시도하면 아래와 같은 이벤트가 순서대로 발생한다.

  touchstart - touchend - mouseover - mousemove - mousedown - mouseup - click

마우스가 존재하진 않지만, 내부적으로는 터치 이벤트 뿐만 아니라 마우스 이벤트도 발생하는 것을 볼 수 있다.
중요한 건, mouseover 시점에서 화면이 변경될 경우, 이벤트가 멈춘다는 것이다.
(이벤트 발생 프로세스의 자세한 내용은 Safari Developer Library의 Handling Events 챕터를 참고.)

위 문서에는 '화면이 변경(if the contents of page changes)'되는 것에 대한 명확한 정의는 없다.
테스트 해본 결과, '화면이 변경'된다는 것은 정확하게는 '레이아웃이 변경(reflow)'되는 것에 가까운 것 같다.
백그라운드가 변경된다거나, 레이아웃에 영향을 끼치지 않는 정도의 margin/padding이 변경되는 경우(repaint)에는 이벤트가 종료되지 않는다.

따라서, 두 번 클릭해야 실행되는 문제를 해결하기 위해서는,
터치 인터페이스에서는 mouseover 이벤트가 발생했을 때 화면 변경이 되지 않도록 해야 한다.


Case by case로?
처음엔 각 케이스 별로, 호버 효과를 위해 추가하는 스타일이나 클래스명을 공백으로 바꿀 생각을 했다.
몇몇 모듈을 테스트 했을 때 문제 없이 적용되었다.
하지만 모듈이 수백 개인 걸... 이건 아니다.


마우스 이벤트를 터치 인터페이스로 바꾸면 어떨까?
마우스 이벤트와 상응하는 이벤트를 터치 이벤트와 매칭시켜주면 어떨까?

  mouseover ---> touchstart
  mousemove ---> touchmove
  mousedown ---> touchend
  mouseup ---> ?  
  mouseout ---> ?


터치 이벤트와 정확하게 매칭되지도 않을 뿐더러, 몇몇 모듈에 테스트해본 결과 기대했던 대로 동작하지도 않았다.
이 방법은 패스~


터치 디바이스일 경우엔, mouse 이벤트를 모두 무시하면 어떨까?
현재 서비스에서는 모든 이벤트를 특정 라이브러리를 통해서 바인딩하고 있다.
이벤트를 바인딩하는 해당 메서드를 오버라이드해서,
터치 디바이스에서 "mouse"를 포함한 이벤트를 바인딩하려는 경우 모두 무시하면 어떨까?

오. 요고 먹힌다.
대부분의 문제가 해결됐다.
다만, click이 아닌 mousedown을 사용하는 몇몇 컴포넌트(drag/drop 같은)는 전혀 작동하질 않는다.

일단은 해결책 중의 하나가 될 수 있겠지만, 의도하지 않은 부분까지도 전혀 작동되지 않는 건 문제가 있다.
화이트 리스트 방식의 적용이 필요하다.


CSS로 hover 효과를 내는 건 어떻게?
마우스 이벤트로 hover를 주는 건은 마우스 이벤트를 제거하는 걸로 해결이 된다 치더라도,
CSS로 hover 효과를 주는 경우엔 제어가 되지 않는다. (CSS와 mouseover 구분하기)

마크업 담당자에 미디어 쿼리(media query)로 터치 디바이스일 경우엔 :hover 를 없애는 건 어떻겠냐고 문의해봤으나, 미디어 쿼리로는 브라우저 구분은 안된다고 한다.

스크립트를 사용해서 CSS 파일을 분기하면 처리할 수 있겠으나,..
이건 배보다 배꼽이 크다는 판단이다.

보통은 클릭 이벤트를 강제로 발생하도록 하는 방법으로 처리할 수 있다고 한다.


클릭 이벤트를 강제로 발생한다?
CSS로 hover가 적용된 경우라면,
mouseover 이벤트에서 이벤트를 중지하고 강제로 한 번 더 click 이벤트를 발생하도록 처리하는 방법이 있다. 
마찬가지로, 이벤트 바인딩 메서드를 오버라이드 해서 구현해봤다.

오. 요고도 먹힌다.
대부분이 문제 없이 잘 실행된다.
하지만, 역시 몇 가지 문제가 있다.

overflow: scroll 속성을 가지고 있는 엘리먼트 내에서 one-finger 스크롤 시, 예기치 않은 이벤트가 계속 발생한다. 
또한, 스크립트로 hover 효과가 적용된 경우라면, mouse 이벤트를 없애는 경우보다 비효율적이다.
터치 때마다 페이지 변경이 계속 발생하기 때문이다.

그리고 mouse 이벤트를 모두 제거했을 때와 마찬가지로 화이트 리스트 방식의 접근이 필요한다.


그렇다면?
최종적으로는, 아래와 같은 방법으로 해결할 수 있었다.

1. 특정 클래스 클래스를 가지고 있는 엘리먼트의 경우, 마우스 이벤트를 할당하지 않는다.
    - 스크립트로 hover 효과를 적용한 경우에 적합하다.
    - 이벤트를 바인딩하는 메서드를 오버라이드 한다.
    - ignore-mouse-event 클래스를 가진 경우, 마우스 이벤트를 할당하지 않는다.
    - mouseover 효과 제거가 필요한 엘리먼트에 해당 클래스를 추가한다.

2. 특정 클래스를 가지고 있는 엘리먼트의 경우, mouseover 이벤트 발생 시 이벤트를 멈추고 강제로 click 이벤트를 발생한다.
    - CSS로 hover 효과를 적용한 경우에 적합하다.
    - document에 mouseover 이벤트를 바인딩한다.
    - touch-force-click 클래스를 가지고 있는 경우, 이벤트를 멈추고 click 이벤트를 발생한다.
    - css로 hover가 적용된 엘리먼트에 해당 클래스를 추가한다.


호버 효과 제거가 필요한 엘리먼트를 찾아 클래스를 추가해야 하는 번거로움이 있긴 하지만, 스크립트 코드 내에 분기문을 추가하는 것보단 훨씬 깔끔하다는 생각이다.
이걸로 삽질 끝~ ㅎ

 

반응형
댓글
공지사항