
안녕하세요!
웹 서핑을 하거나 온라인 강의를 들을 때, 중요한 문장에 형광펜으로 쓱 긋고 나중에 다시 보고 싶다는 생각을 해본 적 없으신가요? 과거에는 이를 구현하기 위해 복잡한 라이브러리를 쓰거나 HTML 구조를 해치는 방식을 사용해야 했지만, 이제는 다릅니다. 오늘은 최신 웹 표준인 CSS 하이라이트 API를 활용해, 성능 저하 없이 나만의 온라인 형광펜 시스템을 구축하는 방법을 깊이 있게 다뤄보겠습니다.
웹 텍스트 강조의 역사와 새로운 패러다임의 필요성
우리가 브라우저에서 특정 텍스트를 시각적으로 강조해 온 방식은 지난 수십 년간 고착화되어 있었습니다. 가장 일반적인 방식은 사용자가 마우스로 드래그한 영역의 노드를 찾고, 그 텍스트를 <span>이나 <mark> 같은 태그로 물리적으로 감싸는 것이었습니다. 하지만 이 고전적인 방식은 현대의 동적이고 복잡한 웹 애플리케이션 환경에서 치명적인 한계를 드러냅니다.
가장 심각한 문제는 ‘DOM(Document Object Model) 트리의 물리적 파편화’입니다. 단 하나의 문장을 강조하기 위해 태그를 삽입하는 순간, 원래 하나였던 텍스트 노드는 최소 세 개 이상의 파편으로 쪼개집니다. 이는 브라우저의 접근성 트리(Accessibility Tree)를 교란시켜 화면을 읽는(Screen Reader) 사용자의 흐름을 방해할 뿐만 아니라, 정규표현식 활용 자바스크립트 텍스트 분석기와 같이 전체 텍스트 구조를 전제로 작동하는 스크립트들이 텍스트 노드 분리로 인해 데이터를 제대로 읽지 못하는 치명적인 버그를 유발합니다.
또한, 수백 개의 하이라이트가 필요한 법률 문서나 기술 명세서 같은 대형 문서에서는 수천 개의 불필요한 DOM 요소가 생성되면서 메모리 점유율이 폭증하고, 레이아웃 리플로우(Reflow)가 발생하여 스크롤 성능이 저하되는 원인이 됩니다. 이러한 배경 속에서 등장한 CSS 하이라이트 API는 기존의 방식을 송두리째 바꾸는 혁신적인 해결책입니다.
CSS 하이라이트 API의 작동 원리와 철학적 차이
CSS 하이라이트 API(CSS Custom Highlight API)의 핵심 철학은 “원본 HTML 구조를 단 한 줄도 건드리지 않는다”는 것입니다. 이 방식은 물리적으로 태그를 삽입하는 대신, 브라우저의 렌더링 레이어 위에 ‘가상의 하이라이트 층’을 덧씌우는 개념입니다. 마치 소중한 종이책 위에 직접 펜을 긋는 대신 투명한 비닐을 덮고 그 위에 표시를 하는 것과 같습니다.

기술적으로는 자바스크립트의 Range 객체와 CSS의 ::highlight() 가상 요소를 결합하여 작동합니다. 2026년 현재, 이 API는 구글이 주도하는 ‘Interop 2026’ 프로젝트의 핵심 포커스 영역으로 선정되어 크롬, 사파리, 파이어폭스 등 모든 모던 브라우저에서 완벽한 호환성을 보장합니다. 이제 개발자는 성능 저하나 구조 왜곡에 대한 걱정 없이, 순수하게 데이터 로직에만 집중할 수 있게 되었습니다.
핵심 기술 요소: Selection과 Range의 정교한 핸들링
이 시스템을 구축하기 위해 가장 먼저 정복해야 할 대상은 사용자의 ‘선택 행위’를 데이터화하는 것입니다. 사용자가 화면을 드래그하면 브라우저는 Selection 객체를 생성합니다. 우리는 이 객체로부터 시작 노드와 끝 노드, 그리고 그 사이의 글자 수(Offset) 정보를 담은 Range 객체를 추출해야 합니다.
// 1. 하이라이트 객체 생성 및 스타일 등록
const myHighlight = new Highlight();
CSS.highlights.set("my-custom-pen", myHighlight);
// 2. 드래그 이벤트 리스너 등록
document.addEventListener("mouseup", () => {
const selection = window.getSelection();
if (!selection.rangeCount || selection.isCollapsed) return;
// 선택된 범위를 Range 객체로 추출
const range = selection.getRangeAt(0);
// 하이라이트 그룹에 추가 (DOM 변경 없음!)
myHighlight.add(range);
// 선택 해제 (형광펜 효과만 남기기 위함)
selection.removeAllRanges();
});
추출된 Range는 일회성 데이터가 아닙니다. 사용자가 다른 곳을 클릭하여 선택을 해제하더라도, 우리는 이 범위를 Highlight 객체에 등록하여 영구적으로 시각화해야 합니다. 이 과정은 자바스크립트 마크다운 프리뷰어 만들기 포스팅에서 텍스트 노드를 추적하던 원리와 같지만, 별도의 DOM 생성 없이 메모리상의 좌표만으로 관리된다는 점에서 훨씬 경제적입니다.
스타일링 혁명: 자바스크립트와 CSS의 역할 분리
과거에는 하이라이트의 색상을 바꾸려면 자바스크립트에서 직접 인라인 스타일을 수정해야 했습니다. 하지만 CSS 하이라이트 API는 스타일 정의를 다시 CSS의 영역으로 돌려줍니다. 자바스크립트는 오직 “어디를 강조할 것인가”라는 논리(Logic)만 담당하고, CSS는 “어떻게 보일 것인가”라는 표현(Presentation)을 담당하는 완벽한 역할 분리가 이루어집니다.
// 핵심 코드: 하이라이트 객체 생성 및 레지스트리 등록
const myHighlight = new Highlight(range);
CSS.highlights.set("my-custom-pen", myHighlight);
이제 CSS에서 ::highlight(my-custom-pen) 가상 요소를 사용하여 배경색, 글자 색상, 텍스트 장식 등을 자유롭게 정의할 수 있습니다. 이는 자바스크립트 MutationObserver 완벽 가이드에서 다뤘던 다크 모드 동기화 시스템과 결합했을 때 진가를 발휘합니다. 자바스크립트 로직 수정 없이 CSS 변수만 교체함으로써 사용자의 테마에 따라 실시간으로 형광펜 색상을 최적화할 수 있기 때문입니다.
CSS 하이라이트 API 실전 데모
중요한 문장을 마우스로 드래그해보세요!
<span> 방식과 달리 DOM 구조를 전혀 변경하지 않으면서도 강력한 시각적 효과를 줍니다.
이 문장을 드래그하는 순간 노란색 형광펜 효과가 적용되는 것을 확인해보세요.
F12 개발자 도구를 열어 요소를 검사해봐도 텍스트 주변에 추가된 태그가 전혀 없다는 점이 이 기술의 핵심입니다!
벤치마크 데이터로 보는 성능의 우위
실제 2026년형 벤치마크 결과에 따르면, 10,000개 이상의 단어를 강조할 때 <span> 방식은 초기 렌더링 시 약 150ms의 지연 시간을 발생시키는 반면, CSS 하이라이트 API는 단 10ms 내외로 처리를 완료합니다. 이는 브라우저가 DOM 트리를 재구성하지 않고 직접 페인트(Paint) 단계에서 하이라이트를 처리하기 때문입니다.
이러한 성능적 이점은 인피니트 스크롤 구현하기: 자바스크립트 성능 최적화 기법을 사용하는 대규모 데이터 대시보드에서 필수적입니다. 수만 개의 데이터를 나열하면서 실시간 검색어 하이라이트를 구현해야 한다면, 이 API 외에는 대안이 없다고 해도 과언이 아닙니다. 진정한 고수 개발자는 사용자 눈에 보이는 기능뿐만 아니라, 보이지 않는 곳에서 소모되는 CPU 자원 하나까지도 이처럼 치밀하게 관리해야 합니다.
데이터 영속성과 암호화: 개인화 도구의 완성
나만의 형광펜 시스템이 실용성을 갖추려면 '저장 기능'이 필수입니다. 새로고침을 할 때마다 강조 표시가 사라진다면 그건 도구가 아니라 장난감에 불과하겠죠. 우리는 각 Range의 정보를 직렬화하여 LocalStorage에 저장해야 합니다. 하지만 여기서 중요한 문제가 발생합니다. 사용자가 어떤 문장을 중요하게 생각하고 강조했는지는 매우 사적인 데이터이며, 때로는 기업의 기밀 정보가 포함될 수도 있습니다.
여기서 자바스크립트 암호화 메모장 제작 가이드에서 강조했던 보안 로직이 다시 한번 등장합니다. 하이라이트 위치 정보를 텍스트 그대로 저장하는 대신, 자바스크립트 암호화 라이브러리인 Web Crypto API(AES-GCM)를 사용하여 데이터를 비문으로 변환한 뒤 저장해야 합니다. 이렇게 하면 브라우저 확장 프로그램이나 악성 스크립트가 로컬 데이터를 탈취하더라도 사용자의 개인적인 학습 기록은 안전하게 보호됩니다. 보안은 단순히 사고를 막는 것이 아니라, 사고가 발생해도 데이터의 가치를 지키는 치밀한 설계에서 완성됩니다.
고급 설계: 중복 하이라이트와 인터랙션 최적화
실제 구현 과정에서 마주치는 가장 큰 난관은 하이라이트 영역이 겹치는 경우입니다. 사용자가 이미 칠해진 영역 위에 또다시 형광펜을 덧칠할 때, 이를 두 개의 독립된 레이어로 둘 것인지 아니면 하나의 합쳐진 영역으로 관리할 것인지에 대한 정책이 필요합니다.
저는 이 문제를 해결하기 위해 바닐라 JS 상태 관리 시스템 만들기에서 활용했던 'Proxy 기반 반응형 엔진'을 도입했습니다. 하이라이트 목록이 변경될 때마다 겹치는 범위를 자동으로 병합(Merge)하여 메모리 효율을 극대화하는 방식입니다. 또한, 특정 하이라이트를 클릭했을 때 삭제 메뉴를 띄우는 기능은 이벤트 위임 패턴으로 최적화한 자바스크립트 OX 퀴즈의 원리를 응용했습니다. 본문에 수백 개의 하이라이트가 있더라도 단 하나의 부모 리스너만으로 모든 상호작용을 제어함으로써 브라우저의 부하를 최소화할 수 있습니다.
CSS 하이라이트 API는 단순한 기능 추가를 넘어, 웹이 문서를 해석하는 방식에 대한 거대한 진보를 상징합니다. DOM이라는 물리적 한계에 갇혀 있던 개발자의 상상력을 렌더링 레이어라는 더 넓은 영역으로 확장해 주었기 때문입니다. 이 기술을 마스터한다는 것은 단순히 '형광펜 기능을 만드는 것'을 넘어, 브라우저의 내부 작동 원리를 깊이 있게 이해하고 최신 웹 생태계의 흐름을 주도한다는 의미이기도 합니다.
이처럼 브라우저의 렌더링 성능을 극대화하여 사용자 경험을 개선하는 방법은 비단 텍스트 강조에만 국한되지 않습니다. 우리가 웹에서 자주 접하는 다른 요소들도 이와 같은 정밀한 로직 설계가 필수적입니다.
예를 들어, 멀티미디어 콘텐츠가 중심인 서비스라면 [자바스크립트 커스텀 비디오 플레이어 제작]을 통해 HTML5 Video API를 직접 제어하며 배속 조절이나 진행바를 앱 수준으로 구현해 볼 수 있습니다. 또한, 0.1초의 오차도 허용하지 않는 정교한 데이터 측정이 필요하다면 [자바스크립트 스톱워치 오차 해결] 포스팅에서 다룬 setInterval의 한계를 극복하는 정밀 로직을 참고해 보시는 것도 좋습니다.
성능, 접근성, 그리고 보안이라는 핵심 가치를 담아낸 이러한 시스템들을 여러분의 프로젝트에 하나씩 도입해 보세요. 사용자들이 정보를 단순히 소비하는 것을 넘어, 자신만의 지식으로 가공하고 안전하게 보관하는 놀라운 경험을 하게 될 것입니다. 끊임없이 진화하는 웹 기술의 파도 속에서 오늘 배운 내용들이 여러분의 실력을 한 단계 더 도약시키는 견고한 발판이 되길 바랍니다. 궁금한 점은 언제든 댓글로 남겨주세요!