FEInterview Prep

Velog

성능과 하이라이팅을 모두 잡는 CSS Highlights API

CSS Custom Highlight APIDOM 을 건드리지 않고 텍스트 영역을 스타일한다. <span> 을 수천 개 뿌리던 기존 하이라이터의 레이아웃 비용을 0 에 수렴시킨다.

2026-02-06·5분 읽기
CSS성능
원문 보기 ↗

핵심 요약

CSS.highlights 는 브라우저가 제공하는 Map<string, Highlight> 전역 레지스트리다. Highlight 는 여러 개의 Range 를 가지고, CSS 에서는 ::highlight(name) 의사 요소로 스타일한다. 이 구조 덕분에 (1) DOM 트리 불변 — 검색 하이라이트를 추가/제거해도 레이아웃이 재계산되지 않는다, (2) 스타일 일괄 적용 — 수천 Range 에도 스타일 규칙은 한 줄, (3) Selection 과 독립 — 사용자 드래그와 충돌하지 않는다. 한계는 (a) 일부 속성만 허용(배경색/전경색/text-decoration), (b) Safari/Firefox 는 최근에야 안정화, (c) Range 기반이라 DOM 이 바뀌면 Range 를 수동 갱신해야 한다는 점.

텍스트 하이라이팅 문제를 'DOM 분할 비용''스타일 적용 비용' 으로 분리한다. 기존 하이라이터(<mark>, <span>) 는 두 비용이 결합돼 노드 수에 선형 비례. Highlights API 는 스타일만 남기고 DOM 을 건드리지 않는다.

검색 결과/코드 편집기/PDF 뷰어에서 '글이 수천 자 + 하이라이트 수백 개' 일 때 스크롤이 뚝뚝 끊기는 원인은 대부분 DOM 분할 이다.

  • innerHTML 재생성으로 리셋 → 레이아웃 스레싱
  • 수백 개 <span> → 스타일 계산 / 레이아웃 / 페인트 전부 비용
  • 선택 영역(Selection) 과 충돌 — 클릭 좌표가 <span> 경계에서 튐

Highlights API 는 DOM 을 안 건드려 세 비용을 전부 회피한다.

학습 포인트

면접 답변으로 연결할 학습 포인트입니다.

Range + CSS.highlights = DOM 불변

const ranges = findAllMatches(text, query).map(toRange);
const highlight = new Highlight(...ranges);
CSS.highlights.set('search', highlight);
::highlight(search) {
  background: #ffec99;
  color: #3f3f3f;
}

<span> 하나도 삽입되지 않는다. 검색 결과가 바뀌면 Highlight 객체의 Range 만 교체 — 레이아웃 재계산 없음.

CSS.highlightsHighlightRange::highlight
자주 하는 오해

document.createRange() 로 만든 Range 를 DOM 수정 후 재사용 하는 것. 노드가 바뀌면 Range 는 stale — 재생성해야 한다.

허용 속성의 제약

::highlight() 에 적용 가능한 속성은 제한적이다.

카테고리허용불가
색상color, background-color-
텍스트text-decoration, text-shadowfont-*, letter-spacing
레이아웃없음padding, margin, border

이유: 레이아웃 영향 속성을 허용하면 DOM 불변 원칙이 깨진다. 폰트 굵기를 바꾸고 싶으면 여전히 <strong> 래핑이 필요.

::highlight허용 속성레이아웃 영향painting only
자주 하는 오해

하이라이트에 padding 이나 border-radius 를 넣어 예쁘게 만들려는 것 — 속성이 무시된다.

우선순위 API(`priority`)

여러 하이라이트가 같은 Range 를 겹치면 Highlight.priority 로 우선순위를 지정한다.

const search = new Highlight(...);
search.priority = 1;

const selection = new Highlight(...);
selection.priority = 2; // 선택이 검색 위에

CSS.highlights.set('search', search);
CSS.highlights.set('selection', selection);

기본값 0. 겹침이 많은 UI(검색 + 하이라이트 + 코멘트) 설계 시 반드시 지정.

priorityz-index 유사stackingoverlapping ranges
자주 하는 오해

우선순위를 생각 안 하고 그려서 어느 색이 위에 올지 브라우저마다 다르게 되는 것.

읽는 순서

  1. 1이론

    CSS Custom Highlight API 명세의 Highlight, CSS.highlights, ::highlight() 세 인터페이스를 각각 한 문장으로 요약.

  2. 2구현

    100KB 텍스트에서 키워드 1000개를 하이라이트하는 데모를 <mark> 버전과 Highlights API 버전 두 가지로 만들고 DevTools Performance 로 비교.

  3. 3실무

    본인 서비스의 검색/코멘트/반응 하이라이트 코드 중 하나를 선택해 Highlights API 로 마이그레이션 PR 을 준비.

  4. 4설명

    팀에 'DOM 을 건드리지 않고 텍스트를 스타일한다 — 왜 이게 성능의 게임체인저인가' 를 5분 발표.

면접 연결 질문

hard검색 결과 하이라이트를 `<mark>` 로 구현하던 코드를 `CSS.highlights` 로 바꿀 때 얻는 **성능상 이점** 을 구체적으로 설명해보세요.
힌트

[좋은 답변] 세 축: (1) 레이아웃 재계산 없음 — DOM 트리 불변, (2) 스타일 재계산 범위 축소 — 페인트 레이어만 갱신, (3) Selection API 와 충돌 없음. 10,000자 문서에 500개 하이라이트 기준으로 스크롤 FPS 비교를 제시하면 만점.

mediumHighlights API 가 **하지 못하는** 일 두 가지를 들고, 대안을 말해보세요.
힌트

[좋은 답변] (1) padding/border 등 레이아웃 영향 스타일 — 필요하면 여전히 <span>, (2) onClick 같은 이벤트 직접 바인딩 — 대신 mouseover + caretPositionFromPoint 로 Range 판정.

hardDOM 이 동적으로 바뀌는 리액트 컴포넌트에서 Highlights API 를 안전하게 쓰려면 어떻게 설계하시겠어요?
힌트

[좋은 답변] (1) Range 는 외부 상태가 아닌 렌더 후 effect 에서 재생성, (2) MutationObserver 로 대상 컨테이너 감시 후 invalidate, (3) 대규모 리스트면 가상화된 창 단위로 Highlight 인스턴스 분리 — 오프스크린 노드는 Range 에서 제외.

자기 점검

`CSS.highlights.set(name, h)` 로 등록한 Highlight 를 제거하는 방법과 `name` 의 네이밍 규칙을 설명해보세요.
CSS.highlights.delete네이밍::highlight전역 레지스트리
자주 하는 오해

Highlight 객체를 버리면 자동 제거된다는 오해. 레지스트리에서 명시적으로 지워야 함.

Highlights API 미지원 브라우저를 어떻게 감지하고 fallback 하시겠어요?
'highlights' in CSSfeature detection<mark> fallbackprogressive enhancement
자주 하는 오해

CSS.supports 로 검사하는 것 — 이건 CSS 속성용. API 존재는 'highlights' in CSS 로 확인.