아래와 같은 문자열이 있을 때 마우스로 드래그해서 텍스트를 선택할 때마다 <span> 태그로 감싸는 작업.
// 초기 상태
<p>Pharetra convallis hendrerit integer nec eleifend tellus luctus lorem dignissim</p>
// convallis hendrerit integer 영역을 드래그했을 때
<p>Pharetra <span>convallis hendrerit integer</span> nec eleifend tellus luctus lorem dignissim</p>
// hendrerit 영역을 드래그했을 때
<p>Pharetra <span>convallis <span>hendrerit</span> integer</span> nec eleifend tellus luctus lorem dignissim</p>
마우스 클릭/텍스트 드래그/클릭 해제 → onMouseUp
이벤트 호출
선택한 영역에 대한 Selection 객체 획득
const selection = window.getSelection();
첫번째 선택 영역에 대한 Range 객체 획득
const range = selection?.getRangeAt(0);
핸들러 실행 조건 검사
range
객체가 없거나 혹은 deleteMode
여부
if (!range || deleteMode) return;
선택한 영역의 .selected
클래스 포함 여부 (중첩 드래그 방지)
if (range.cloneContents().querySelector('.selected')) return;
// range.cloneContents 메서드는 Range에 포함된 Node 객체를 복사한 후 DocumentFragment 반환
// 드래그 영역 : convallis hendrerit integer nec (hendrerit는 <span>으로 랩핑되어 있음)
// 반환값 : "convallis "<span class="selected">hendrerit</span>" integer"
range.cloneContents.querySelector
는 document.querySelector
셀렉터와 사용법 동일(첫번째 선택된 엘리먼트 혹은 선택된 엘리먼트 없으면 null
반환)range.start|endContainer
: Range 시작|종료 노드 반환range.start|endContainer.parentNode
: Range 시작|종료 노드의 부모 요소 반환선택한 텍스트를 랩핑할 새로운 <span>
태그 생성
const selectedText = range.toString(); // 드래그한 영역의 문자열 반환
const span = document.createElement('span');
span.className = 'selected';
span.textContent = selectedText;
기존 선택한 텍스트 노드 삭제 후 생성한 <span>
태그로 대체
range.deleteContents();
range.insertNode(span);
마우스 클릭 → 이벤트 핸들러 실행
<aside> <img src="/icons/search_gray.svg" alt="/icons/search_gray.svg" width="40px" />
이벤트 핸들러는 부모 요소에만 할당 (이벤트 버블링으로 클릭 이벤트가 부모로 전파되므로)
</aside>
<!-- 아래 요소에서 hello를 클릭했다고 가정 -->
<span class="selected">hello <span class="selected">world</span></span>
핸들러 실행 조건 검사
// deleteMode 상태이고, 클릭한 타겟 요소내 selected 클래스를 포함할 때만 실행
if (!deleteMode || !target.classList.contains('selected')) return;
클릭한 요소의 innerHTML 복사
// 이 과정에서 클릭한 요소 가장 바깥의 시작/종료 태그는 포함하지 않음
// 클릭한 요소 : <span>hello <span>world</span></span>
// innerHTML : hello <span>world>/span>
const spanContent = target.innerHTML;
임시로 사용할 요소 생성
const textNode = document.createElement('span');
복사한 콘텐츠를 임시 생성한 요소의 innerHTML에 복사
textNode.innerHTML = spanContent;