CSS Custom Hightlight API
JavaScript를 사용하여 범위를 만들고 CSS와 함께 해당 텍스트의 범위를 스타일링하는 API
텍스트 편집기에서 오타, 문법 오류, 구문 오류 등 강조 가능
현재 Firefox 외의 브라우저에서 지원한다.
적용 방법
1. Range 생성하기
JavaScript의 Range 객체를 사용하여 시작 범위와 종료 범위를 설정한다.
const parentNode = document.getElementById("foo");
const range1 = new Range();
range1.setStart(parentNode, 10); // 10번의 위치(offset)부터
range1.setEnd(parentNode, 20); // 20번의 offset까지 range1
const range2 = new Range();
range2.setStart(parentNode, 40); // 40 offset부터
range2.setEnd(parentNode, 60); // 60 offset까지 range2
2. 범위에 대한 Highlight 생성하기
const highlight = new Highlight(range1, range2);
3. HightlightRegistry를 사용하여 highlight 등록하기
CSS.highlights.set("user-1-highlight", user1Highlight);
CSS.highlights.set("user-2-highlight", user2Highlight);
// Remove a single highlight from the registry.
CSS.highlights.delete("user-1-highlight");
// Clear the registry.
CSS.highlights.clear();
4. ::hightlight()에 스타일링하기
::highlight(user-1-highlight) {
background-color: yellow;
color: black;
}
적용하기
간단하게 말해서, HTML에서 노드를 불러온 후 해당 노드의 text에 따른 범위 객체를 생성한 후 hightlight 객체를 만들어 CSS.hightlists에 넣어주고 스타일링을 입혀주면 된다.
// HTML
<div id="foo">Life is a mountain.
Your goal is to find your path,
not to reach the top. – Maxime Lagacé</div>
// CSS
::highlight(highlight) {
background-color: pink;
color: black;
}
// JS
const parentNode = document.getElementById("foo");
const textNode = parentNode.firstChild;
const range1 = new Range();
range1.setStart(textNode, 10);
range1.setEnd(textNode, 18);
const range2 = new Range();
range2.setStart(textNode, 46);
range2.setEnd(textNode, 50);
const highlight = new Highlight(range1, range2);
CSS.highlights.set("highlight", highlight);
코드 수정하기
이전코드
현재 검색결과를 props로 받아와서 map으로 각각의 텍스트 요소를 키워드로 slice하여 표시하고 있다.
export default function SearchResultList() {
...
return (
<div className={styles.container}>
{keywordSearchResult &&
keywordSearchResult?.length !== 0 &&
keywordSearchResult.map((keyword) => (
<div key={keyword.id}>
{keyword.name.split(currentKeyword.name)[0]}
<span>{currentKeyword.name}</span>
{keyword.name.split(currentKeyword.name)[1]}
</div>
))}
</div>
);
}
수정한 코드
범위를 설정할 때 node를 추가해줘야해서 useEffect를 이용하여 현재 검색 키워드가 변경될 때마다 kewordSearchList를 전부 가져온 후 각각의 하이라이트를 추가해주었다.
export default function SearchResultList() {
...
useEffect(() => {
if (!window.CSS || !CSS.highlights) {
console.warn("CSS Custom Highlight API 사용불가능");
return;
}
const highlight = new Highlight();
document.querySelectorAll(".highlight").forEach((el) => {
const textNodes = Array.from(el.childNodes).filter(
(node) => node.nodeType === Node.TEXT_NODE,
);
textNodes.forEach((node) => {
const text = node.textContent;
if (!text) return;
const range = new Range();
const startIndex = text.indexOf(currentKeyword.name);
if (startIndex === -1) return;
const endIndex = startIndex + currentKeyword.name.length;
range.setStart(node, startIndex);
range.setEnd(node, endIndex);
highlight.add(range);
CSS.highlights.set("highlight", highlight);
});
});
}, [currentKeyword.name, keywordSearchResult]);
return (
<div className={styles.container}>
{keywordSearchResult &&
keywordSearchResult.length !== 0 &&
keywordSearchResult.map((keyword) => (
<div key={keyword.id} className="highlight">
{keyword.name}
</div>
))}
</div>
);
}
장단점 비교
CSS Custom Highlight API의 경우 브라우저 엔진이 최적화된 방식으로 강조 표시를 하는 것이 가장 큰 차이점이다. 이외에 동적으로 생성된 텍스트 노드에도 즉시 강조를 적용할 수 있다는점, 여러 하이라이트를 중첩으로 사용할 수 있다는 점 등이 있다. 해당 복잡성이 커질수록 slice보다 유용하게 사용할 수 있고 유지보수가 편리해진다. 다만 firefox에서는 지원하지 않는다는 점을 유의해야한다. Strin.slice를 이용하면, 모든 브라우저에서 호환가능하며, 작은 프로젝트의 경우 직관적이고 단순하다. 다만 slice를 사용하면 대규모 텍스트 처리나 동적 콘텐츠 업데이트가 많은 경우 유지보수가 어려울 수 있다. 현재는 작은 프로젝트이기 때문에 slice로 충분히 대체할 수 있으나, 커질 가능성이 있다면 CSS Custom Highlight API를 사용하는 것이 좋아 보인다.
Reference
'트러블이슈 > 이약저약' 카테고리의 다른 글
createPortal로 모달을 바깥에 띄우기 (0) | 2024.05.30 |
---|---|
테스트코드 작성기 (0) | 2024.05.16 |
에러 찾다가 react-hook-form 탐험하기 (0) | 2024.05.08 |
CSS Cascade 알아보기 (0) | 2024.04.30 |
컴파운드 컴포넌트 패턴으로 모달 만들기 (0) | 2024.04.24 |