정의
사용자가 글자를 드래그로 선택하면 그 위에 떠 있는 툴바(플로팅 툴바 — 굵게·기울임 같은 버튼이 뜨는 작은 팝업) 를 띄우고 싶을 때, 그 툴바를 언제 보여줄지 정하는 방법에 관한 패턴이다.
흔히 쓰는 방식은 마우스 버튼을 누를 때(mousedown) 툴바를 숨기고 뗄 때(mouseup) 다시 띄우는 것인데, 이 글은 그 대신 document의 selectionchange 이벤트(브라우저에서 선택 영역이 바뀔 때마다 발생하는 이벤트)로 갱신하자는 이야기다. 선택 범위가 바뀔 때마다 툴바에 필요한 정보 묶음(snapshot — 선택이 비었는지, 화면상 위치 rect, 굵게 여부 같은 상태를 한 번에 모아둔 것)을 다시 계산하므로, 드래그하는 도중에도 툴바를 띄울 수 있다.
왜 필요한가
마우스를 누를 때 숨기고 뗄 때 보여주는 방식은 "선택이 완전히 끝난 뒤에야 툴바가 뜨는" 경험이 된다. 드래그하는 동안 실시간으로 하이라이트를 보여주거나 곧바로 포맷 버튼을 띄우고 싶다면, 마우스 상태가 아니라 선택 그 자체의 변화에 맞춰야 한다.
게다가 마우스 이벤트에만 의존하면 잘 깨진다: HTML5 드래그 앤 드롭(요소를 끌어다 놓는 브라우저 기본 기능)은 드롭이 끝난 뒤 mouseup을 안 보내는 경우가 있어서, "눌렀는데 떼는 신호가 안 온" 상태로 툴바 표시 로직이 멈춰버릴(stuck) 수 있다. selectionchange는 이 함정을 피한다.
동작 원리
- 사용자가 pointer로 텍스트를 드래그한다.
- 브라우저가 Selection range를 갱신하고
selectionchange를 발화한다. - 핸들러가
window.getSelection()·anchor/focus node를 읽어 툴바 snapshot(비었는지, rect, marks)을 계산한다. - 비어 있지 않으면 플로팅 UI를 렌더한다.
- 하이브리드 에디터에서는 anchor/focus가 “현재 guest container” 밖이면 단일블록 툴바는 숨기고, 상위 range-level 툴바가 소유한다 — 이중 UI 방지.
selectionchange는 스크롤을 발생시키지 않는다 — anchor 좌표 drift는 scroll 리스너로 별도 처리한다.
실무 적용
isSelectingText+ mousedown/mouseup +requestAnimationFrameband-aid 제거.- 단일블록 guest는 editor 트랜잭션으로 bold/italic 유지; 크로스 경계만 document-level handler + patch.
- 테스트:
document.execCommand('selectAll')또는 programmatic selection 후 툴바가 mouseup 없이 나타나는지 확인. - Storybook·브라우저에서 드래그 선택·크로스 경계 선택·이중 툴바 없음을 육안 검증.
트레이드오프
selectionchange는 입력 중 자주 발화할 수 있다 — snapshot 빌드는 가볍게, rect equality로 bail.- shadow DOM·여러 contenteditable이 있으면 container 경계 판별이 필요하다.
- 전부 patch 경로로 통합하면 기존 align·PM semantics 회귀 위험 — 단일블록은 guest 유지가 안전한 경우가 많다.
사용하면 안 되는 경우
- 선택과 무관한 고정 툴바(항상 visible).
- 블록 재정렬 DnD 핸들만 있는 UI — 텍스트 selection이 없어 포맷 툴바가 무의미.
- “항상 표시”가 요구되지만 실제로는 빈 selection —
!emptyself-gate 유지.
흔한 실수
- 블록 DnD와 텍스트 selection UI를 같은 mouse 플래그로 묶음.
- 크로스 경계에서 단일·range 툴바 동시 표시(container 가드 누락).
- selectionchange만으로 scroll anchor까지 해결하려 함.
- jsdom에서 layout rect 없음 → 시각 검증을 브라우저로 미루지 않음.
관련 개념
- fixed-position-floating-ui-scroll — 스크롤 시 anchor 재측정
- focused-guest-editor — guest 한 블록 lease
- contenteditable-keyboard-history-delegation — 키보드·히스토리 위임