2023-06-25 00.27.40.gif

아래와 같은 문자열이 있을 때 마우스로 드래그해서 텍스트를 선택할 때마다 <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>

드래그한 문자열 랩핑


  1. 마우스 클릭/텍스트 드래그/클릭 해제 → onMouseUp 이벤트 호출

  2. 선택한 영역에 대한 Selection 객체 획득

    const selection = window.getSelection();
    
  3. 첫번째 선택 영역에 대한 Range 객체 획득

    const range = selection?.getRangeAt(0);
    
  4. 핸들러 실행 조건 검사

    1. range 객체가 없거나 혹은 deleteMode 여부

      if (!range || deleteMode) return;
      
    2. 선택한 영역의 .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.querySelectordocument.querySelector 셀렉터와 사용법 동일(첫번째 선택된 엘리먼트 혹은 선택된 엘리먼트 없으면 null 반환)
      • range.start|endContainer : Range 시작|종료 노드 반환
      • range.start|endContainer.parentNode : Range 시작|종료 노드의 부모 요소 반환
  5. 선택한 텍스트를 랩핑할 새로운 <span> 태그 생성

    const selectedText = range.toString(); // 드래그한 영역의 문자열 반환
    const span = document.createElement('span');
    span.className = 'selected';
    span.textContent = selectedText;
    
  6. 기존 선택한 텍스트 노드 삭제 후 생성한 <span> 태그로 대체

    range.deleteContents();
    range.insertNode(span);
    

랩핑한 태그 삭제


  1. 마우스 클릭 → 이벤트 핸들러 실행

    <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>
    
  2. 핸들러 실행 조건 검사

    // deleteMode 상태이고, 클릭한 타겟 요소내 selected 클래스를 포함할 때만 실행
    if (!deleteMode || !target.classList.contains('selected')) return;
    
  3. 클릭한 요소의 innerHTML 복사

    // 이 과정에서 클릭한 요소 가장 바깥의 시작/종료 태그는 포함하지 않음
    // 클릭한 요소 : <span>hello <span>world</span></span>
    // innerHTML : hello <span>world>/span> 
    const spanContent = target.innerHTML;
    
  4. 임시로 사용할 요소 생성

    const textNode = document.createElement('span');
    
  5. 복사한 콘텐츠를 임시 생성한 요소의 innerHTML에 복사

    textNode.innerHTML = spanContent;