통상적인 브라우저가 감지할 수 있는 MouseEvent 종류 중에서 mouseOver 와 mouseEnter에 대해서 정리해본다.
mouseOver와 mouseEnter는 어떤 요소 안으로 마우스가 들어오는 순간을 감지하는 마우스 이벤트이며, 이와 반대로 마우스가 어떤 요소 밖으로 이동하는 순간을 감지하는 마우스 이벤트는 mouseOut과 mouseLeave다.
일반적으로 mouseOver는 mouseOut와 짝을 이루고, mouseEnter는 mouseLeave와 짝을 이루어 사용한다.
주로 아래와 같이 짝을 이루어 사용한다.
mouseOver <-> mouseOut
mouseEnter <-> mouseLeave
두 이벤트를 유사하지만 이벤트 전파(event propagation)와 취소 가능성(cancelable)에 큰 차이가 있다.
mouseOver/mouseOut는 이벤트가 발생할 때 버블링이 일어나며 상위 요소로 전파된다. 그리고 preventDefault 메서드를 호출하여 이벤트의 기본 동작을 취소할 수 있다.
한편 mouseEnter/mouseLeave는 이벤트가 발생할 때 버블링이 일어나지 않아 자기 자신만이 이벤트를 받을 수 있게 된다. 즉 target과 currentTarget이 항상 일치한다. 또한 preventDefault 메서드를 호출하여 이벤트의 기본 동작을 취소할 수 없다. (bubbles: false; cancelable: false;)
자세한 내용은 MDN의 mouseover / mouseenter 를 참고하자.
아래는 이를 간단하게 표현할 수 있는 예제다. (출처: https://findfun.tistory.com/290)
숫자가 증가되는 순간을 자세히 살펴보라.
이벤트 전파 과정에 대해 알고 있다면 이는 너무도 당연한 이야기다.
그런데 레이아웃을 구성하다보면 자식 요소가 부모 요소 바깥에 위치할 수 있다.
이 경우 별다른 고민없이 mouseOver/mouseOut 이벤트를 적용하다보면 예상치 못한 버그가 생길 수 있다.
아래 예제를 살펴보자.
outer #2의 자식 요소인 빨간색 inner 박스가 화면 상 outer#1 위에 존재하고 있다.
이 빨간색 요소에 마우스 이벤트가 발생했을 때 레이아웃 상 뒤쪽에 위치한 outer#1에 마우스 이벤트가 전파될까?
당연히 그렇지 않다.
두 가지 이유가 있다. inner 박스의 조상이 outer#2이며, 마우스 이벤트가 가장 위에 있는 요소에서만 동작하기 때문이다. 만약 inner 박스가 pointer-events: none; 로 설정되어 있다면 outer#2는 이벤트를 받지 못하고 outer#1가 이벤트를 받게 될 것이다. (위 예제의 css .inner 주석을 수정하여 시험해 볼 수 있다.)
여기서는 MouseOver 이벤트로 예를 들었지만 MouseDown이나 Click 일 경우에도 동일한 버그가 생길 수 있다는 점을 명심하자. (예제의 mouseoever를 click으로 바꾸어보라)
직전 예제에서 살펴본 현상은 이벤트 버블링이 일어나는 모든 MouseEvent에 해당되는 이야기다.
이 특징 때문에 마우스 이벤트와 버블링으로는 구현할 수 없는 간단한 시나리오가 있다.
어떤 div 요소A를 드래그하여 이동한 후 드롭할 때, 드롭된 위치에 있는 다른 요소B가 반응할 수 있도록 그 요소B를 알고 싶다고 하자.
혹자는 요소B에서 mouseUp 이벤트를 감지하면 된다고 생각할 수 있다. 하지만 앞서 예제를 살펴보았다면 이 방법으로는 불가하다는 걸 알 수 있다. 아래 예제에서 빨간 inner 박스를 드래그 한 후 어디에서 mouseUp해도 항상 그 부모가 mouseUp 이벤트를 감지한다.
위와 같은 구현이 필요한 경우가 드물 것이라 생각되지만 혹시나 필요한 사람을 위해 DragEvent를 이용한 방법과 elementsFromPoint(x,y) 함수를 사용한 방법을 공유하며 포스팅을 마친다.