정의
선택한 글자 옆이나 클릭한 버튼 옆에 뜨는 작은 UI(팝오버·툴바)가, 열어 둔 채로 페이지를 스크롤하면 원래 붙어 있던 자리에서 떨어져 엉뚱한 곳에 남는 것을 고치고 싶을 때 이 개념을 쓴다.
Fixed-position floating UI scroll drift는 선택·클릭 위치에 붙는 UI가 position: fixed(화면 뷰포트 기준으로 고정되어 스크롤해도 안 움직이는 CSS 배치)** 앵커**(floating UI가 붙을 기준점이 되는 보이지 않는 요소)** element에 열릴 때 한 번만 캡처한 top/left(또는 getBoundingClientRect, 요소의 화면상 위치·크기를 재는 브라우저 함수)** 를 유지할 때, 사용자가 스크롤하면 본문은 움직이지만 앵커는 뷰포트(브라우저에서 실제로 보이는 화면 영역)의 과거 좌표에 남아 팝오버·포맷 툴바가 하이라이트에서 벗어나는 현상이다.
왜 필요한가
Popper·Radix·Floating UI는 앵커의 현재 rect(요소의 위치·크기 사각형)에 content를 정렬한다. 앵커 좌표가 stale(오래돼 실제와 어긋난 상태)이면 라이브러리는 그대로 "정확히" 잘못된 위치를 따른다. selectionchange(사용자가 텍스트 선택 영역을 바꿀 때 발생하는 이벤트)는 스크롤을 발생시키지 않으므로, 선택 기반 UI와 scroll 추적은 별도로 처리해야 한다.
동작 원리
- 툴바 open 시 selection/caret 위치를 측정해 invisible fixed anchor를 배치한다.
- 사용자가 window 또는 중첩
overflow: auto컨테이너를 스크롤한다. - 텍스트·하이라이트는 문서 좌표계에서 이동하지만, anchor의
top/left는 갱신되지 않는다. - Floating layer는 stale anchor rect 기준으로 layout된다.
scroll이벤트는 bubble(자식→부모로 이벤트가 위로 전파)하지 않지만 capture phase(부모→자식으로 내려가며 이벤트를 먼저 가로채는 단계)에서 window listener로 중첩 스크롤러까지 잡을 수 있다.
실무 적용
- UI가 열려 있는 동안만 scroll(capture) + resize 리스너를 등록하고 cleanup한다.
- 링크 팝오버처럼 특정 element에 붙는 경우 클릭한 DOM 노드 ref를 보관하고
getBoundingClientRect()로 재측정; 노드가 제거되면 닫는다. - rect가 이전과 같으면 setState를 생략해 불필요한 리렌더를 줄인다.
- 장문서에서 jank(스크롤·애니메이션이 매끄럽지 않고 뚝뚝 끊기는 현상)가 보이면
requestAnimationFrame(다음 화면 그리기 직전에 콜백을 실행시켜 주는 브라우저 API) throttle(호출 빈도를 제한)을 검토한다. - jsdom 테스트: layout API mock +
fireEvent.scroll(window)로 좌표 추종을 검증한다.
트레이드오프
- scroll마다 측정은 저렴한 경우가 많지만, 수천 개의 동시 open UI에서는 비용이 쌓인다 — open 상태에서만 리스너를 둔다.
- capture scroll은 모든 스크롤러에서 발화하므로 핸들러는 가볍게 유지한다.
- “스크롤 시 닫기”는 구현은 쉬우나 UX가 다르다 — 요구가 “따라가기”면 재측정이 맞다.
사용하면 안 되는 경우
- viewport에 고정된 전역 헤더·토스트 — 선택 앵커가 아님.
- 스크롤 컨테이너 밖 absolute positioning으로 이미 문서 좌표를 추적하는 경우.
- 네이티브
position: fixed+ CSSanchor-name(지원 환경)으로 브라우저가 추적하는 최신 API — 폴리필·타깃 브라우저 확인 필요.
흔한 실수
selectionchange만 듣고 scroll은 무시.- bubble phase scroll만 등록해 중첩 스크롤러 miss.
- open 시 1회 측정 후 Radix에 “버그”로 오인.
- 테스트 환경에 layout이 없어 좌표가 0인데 프로덕션만 검증.
관련 개념
- selectionchange-floating-ui — 선택 UI 트리거
- radix-portal-scoped-css — Portal·scoped CSS (별개 이슈)
- html5-drag-and-drop — mouseup 억제와 선택 UI