사파리 브라우저 환경에서 특정 기능이 정상적으로 동작하지 않는 것을 발견했다. 비지니스 로직, UI/UX 로직, 서드 파티 라이브러리 등 연관된 코드들을 디버깅 하면서 검토했다. 또한, 이벤트들이 해당 브라우저의 버전에서 정상적으로 동작하는지 MDN을 참고하여 지원 여부도 확인했다. 하지만 문제가 발생할만한 부분은 없었다. "도대체 뭐가 문제야?!"라는 소리와 함께, 한숨이 나올때 쯤 눈에 뭔가 보였다.
실제 서비스의 기능은 설명하지 않고, 이해하기 쉬운 예제로 대체했다.
포켓몬의 정보를 볼 수 있는 페이지다. 고객이 잡은 4마리의 포켓몬 데이터가 좌측에 표시되고 있다. 자세한 정보를 보고 싶은 포켓몬을 클릭하면, 해당 포켓몬에 대한 정보가 우측에 나타난다. 파이리를 클릭하면, 파이리에 대한 상세 페이지가 나타나는 것이다.
이번에는 포켓몬 조합을 통해 최강의 몬스터를 만드는 페이지다. 포켓몬 조합을..?
이라는 생각이 들겠지만 그냥 넘어가자. 조합하고 싶은 포켓몬 2종류를 드래그 앤 드롭으로 우측에 옮기면 되는 것이다. 좌측을 보면, 포켓몬 정보 페이지에서 사용했던 컴포넌트와 유사하다. D&D 기능만 추가된 것이다. 즉, 포켓몬을 누르면 우측에 포켓몬의 상세 정보를 넘겨줄 수 있도록 기능이 설계되어 있고, 상황에 따라서는 D&D기능을 사용할 수 있도록 구현했다.
리스트 컴포넌트를 구현하여 크롬에서 정상적으로 동작하는 것을 확인했지만, 사파리에서 D&D 기능이 동작하지 않았다. 왜 동작하지 않았는지, 간단한 코드를 보며 공유하겠다.
<ul>
<li>
<a>파이리</a>
</li>
</ul>
마크업의 구조는 이와 같다. 유저가 잡은 포켓몬의 데이터로 li
반복문을 돌리며 출력하고 있다. a
태그 안에 더 많은 마크업들이 있겠지만, 쉬운 예시를 위해 포켓몬의 이름만 넣었다.
포켓몬 정보 페이지에서, 특정 포켓몬을 클릭했을 경우 상세 페이지를 보여주고 있었다. 그래서 a
태그가 적절한 태그라고 생각하여 li
태그 하위에 위치시켰다.
<ul>
<li draggable=...>
<a>파이리</a>
</li>
</ul>
포켓몬 리스트 컴포넌트가 드래그가 가능한 컴포넌트로써 사용되고 있는지에 대한 체크를 하고, 그에 맞는 값을 li
태그 draggable
에 바인딩하고 있다. 그리고 해당 태그에서 dragStart
, dragEnd
등 브라우저 네이티브 이벤트가 터지면서 실제 D&D 기능이 동작할 수 있도록 함수들을 핸들링하고 있다. (위 코드에서는 생략)
<ul>
<li draggable={...}>
<a onClick={...}>파이리</a>
</li>
</ul>
a
태그에서는 클릭 이벤트의 기본 동작을 방지하기 위해, 클릭 핸들러 함수에 e.preventDefault를 추가했다. (실제로는 Vue를 사용하여 @click.prevent.stop을 사용했지만, 이해를 돕기 위해 React로 설명하고 있다.) 바로 이게 문제가 되었다.
@click.prevent
기본 동작 방지 (=preventDefault)
@click.stop
이벤트 전파 방지 (=stopPropagation)
@click.prevent.stop
이벤트 객체의 preventDefault와 stopPropagation 메소드 둘 다 실행
유저가 포켓몬 리스트의 특정 요소를 드래그한다고 가정해보자. 예상 동작은 클릭 -> 클릭한 상태로 드래그 -> 우측 UI에 드롭
일 것이다. 먼저 a
태그에 클릭 이벤트가 발생하고, 기본 클릭 이벤트 동작은 방지했으므로 아무런 일도 일어나지 않는다. 그리고 그 상위의 li
태그에서 구현된 D&D 기능은 정상적으로 동작할 것이다. 실제로 Chrome, IE 등의 브라우저에서는 정상적으로 동작했다. 하지만 Safari에서는 동작하지 않았다.
정확한 이유를 찾지는 못했다. 그래도 생각보면, 브라우저마다 이벤트 전파 방식의 차이가 있어, 이와 같은 문제점이 발생했다고 밖에 생각할 수 없었다. preventDefault
속성을 부여했던 a
태그를 제거하고, button
으로 변경했더니 크롬, IE, 사파리 등에서 모두 정상적으로 동작했기 때문이다.
이런 유용한 정보를 나눠주셔서 감사합니다.