드래그와 UI

#기술 #UI

얼마전 React로 채팅창의 드래그가 가능한 윈도우의 타이틀 바 UI를 개발했는데, 다음과 같은 일이 일어났다:

  1. 드래그를 하면서 드롭다운 위를 움직이면 타이틀 바 안에 있는 드롭다운이 동작하는 버그를 발견했다.
  2. 그래서 드래그 중에는 드롭다운이 동작하지 않도록 타이틀 바 컴포넌트에 isDragging 플래그를 추가했다. 드롭다운 컴포넌트는 isDragging 플래그를 참조하면서 동작을 할 지 결정한다.
  3. 2.의 수정 덕분에 대부분의 경우는 괜찮은데, 드롭다운을 누를 때 가끔 드래그로 인식해서 드롭다운이 동작을 안 하는 버그가 있었다.

근본적인 원인은 타이틀 바의 드래그 영역때문이었다. HTML DOM 중에서 가장 상위에 있는 <div>에서 ondrag 이벤트를 처리하고 있었기 때문에, 하위 DOM인 드롭다운에서 드래그 이벤트가 발생하면 이벤트 버블링을 막아주어야 한다. 또한 드롭다운 위에서 일어나는 드래그 동작은 무시되어야 할 것이다. 그런데 만약 여기에 드롭다운 말고 여러 컴포넌트가 더 있다면 같은 일을 반복하는 귀찮은 일이 된다. 더구나 새로운 컴포넌트가 추가될 때 다른 개발자가 이 사실을 모른다면 이러한 버그는 또 발생할 것이다.

내가 생각한 가장 쉬운 해결책은 투명한 배경영역 <div>를 하나 더 만들어서 <div>에 드래그 핸들러를 붙이는 것. 드래그 영역은 대부분 전경이 아닌 배경이기 때문에, 배경에 해당하는 영역을 DOM 트리상 같은 부모 아래에 하나 만든다. 드래그 영역과 전경이 부모 자식관계가 아닌 형제관계(sibling)에 놓이면 서로의 이벤트가 버블링될 염려가 없기 때문이다. 이때 드래그를 위한 <div>는 다른 요소들보다 아래에(낮은 z-index) 위치해야 한다. 이것을 간단한 수도 코드로 작성하면 다음과 같다.

<div class="header">
  <div class="draggable" style="
    background: transparent;
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    width: 100%;
    z-index: -1;
  "/>
  <img src="avatar" />
  <input class="dropdown" />
  <button class="minimize window-button" />
  <button class="maximize window-button" />
  <button class="close window-button" />
</div>

macOS의 윈도우 타이틀 바에는 신호등같은 버튼들이 놓여있는데 이것들을 드래그할 경우 .highlighted(CSS 셀렉터로 말하면 :active상태)가 된 다음 범위를 벗어나면 일반적인 상태로 돌아온다. 이것이 드래그에 대처하는 바람직한 UI 컴포넌트의 인터랙션이 아닐까.

하지만 어떤 UI 컴포넌트들은 드래그와 동시에 클릭 동작을 인식해버려서 마치 클릭했을 때와 같은 이상한 동작을 하는 경우가 있는데, 제대로 된 UI 컴포넌트라면 드래그 인터랙션도 올바르게 정의되어 있어야 할 것이다.