Mobile Web iPhone Hover Click

iPhone Safari 에서 클릭을 두 번 해야만 동작 이슈

먼저 자세한 증상 및 원인과 해결방법은 아래와 같다.

증상 : 클릭시 첫번째 클릭은 동작안함(두번째부터 동작함)
원인 : css의 허위클래스 :hover 선언시 문제발생으로 마우스 오버시 클릭하면 이벤트가 동작안함
디바이스 : 아이폰 safari 브라우저

왜 두 번 클릭해야 하는 현상이 생기는 것인가?

왜 두 번 클릭해야 하나?
먼저, hover 효과가 적용된 엘리먼트에서 두 번 클릭이 필요한 이유에 대해 자세히 알아보자.
모바일 사파리에서 터치를 시도하면 아래와 같은 이벤트가 순서대로 발생한다.

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

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

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

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

마우스 이벤트를 터치 인터페이스로 바꾸면 어떨까?

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

1
2
3
4
5
mouseover -> touchstart
mousemove -> touchmove
mousedown -> touchend
mouseup -> ?
mouseout -> ?

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

터치 디바이스일 경우엔, mouse 이벤트를 모두 무시하면 어떨까?

현재 서비스에서는 모든 이벤트를 특정 라이브러리를 통해서 바인딩하고 있다. 이벤트를 바인딩하는 해당 메서드를 오버라이드해서, 터치 디바이스에서 mouse를 포함한 이벤트를 바인딩하려는 경우 모두 무시하면 어떨까?
대부분의 문제가 해결됐다. 다만, click이 아닌 mousedown을 사용하는 몇몇 컴포넌트(drag/drop 같은)는 전혀 작동하질 않는다.

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

CSS로 hover 효과를 내는 건 어떻게?

마우스 이벤트로 hover를 주는 건은 마우스 이벤트를 제거하는 걸로 해결이 된다 치더라도, CSS로 hover 효과를 주는 경우엔 제어가 되지 않는다.
스크립트를 사용해서 CSS 파일을 분기하면 처리할 수 있겠으나, 이건 배보다 배꼽이 크다는 판단이다.
보통은 클릭 이벤트를 강제로 발생하도록 하는 방법으로 처리할 수 있다고 한다.

클릭 이벤트를 강제로 발생한다?

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

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

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

해결방법1

아래와 같은 방법으로 해결할 수 있다.

1.특정 클래스 클래스를 가지고 있는 엘리먼트의 경우, 마우스 이벤트를 할당하지 않는다.

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

2.특정 클래스를 가지고 있는 엘리먼트의 경우, mouseover 이벤트 발생 시 이벤트를 멈추고 강제로 click 이벤트를 발생한다.

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

해결방법2

해결방법은 간단하다. 모바일 환경인 경우 css의 :hover가 동작하지 않도록 설정하는 방법이다. 이 경우 미디어 쿼리를 사용할 수 있을 것이다. 몇 가지 해결방법들에 대하여 알아보자.

1. 모바일인 경우 touch와 같은 클래스 추가하여 해결하기

hover 이벤트를 막는 방법으로 모바일 환경인 경우 이를 인지할 수 있는 클래스를 선언하여 :hover인 경우 아무 동작이 없도록 하는 방법이 있다. 예를들어 모바일 환경이면 스크립트를 사용하여 body 태그에 touch 라는 클래스를 추가할 수도 있다.
아니면 미디어쿼리(media-query)를 사용해서 모바일 환경에서는 마우스 오버 스타일을 동작하지 않도록 코드를 추가한다.

1
2
3
@media only screen and (min-width: 768px) {
button {}
}

2. :hover는 반드시 mobile이 아닌 경우에만 동작하도록 하기

이 역시 데스크탑에서만 가능한 미디어쿼리를 적용할 수 있다. 마우스 오버의 스타일 정의는 오직 모바일이 아닌 경우만 가능하도록 한다.

1
2
3
@media only screen and (max-width: 768px) {
button:hover {}
}

3. 모바일 전용 페이지인 경우 모든 hover 제거하기

만약 반응형이 아닌 모바일 페이지라면 :hover를 사용할 필요가 없을 것이다. 이런 이유로 모바일 전용 페이지의 마우스 오버 스타일코드를 제거한다.

참조

공유하기