728x90

[JavaScript] Selection과 Range

자바스크립트

JavaScript를 사용해 document 내의 선택된 항목에 접근해서 DOM 노드의 전체 또는 선택된 부분을 가져오는 등의 작업을 할 수 있습니다.

SelectionRange는 WYSIWYG 에디터, 리치 에디터 등을 구현할 때 매우 중요하게 사용됩니다.

TLDR

document 내의 선택된 콘텐츠의 정보를 가져오기 위해 SelectionRange 객체를 사용할 수 있습니다.

input, textarea 엘리먼트의 경우, element.selectionStartelement.selectionEnd 값을 설정해 조작할 수 있습니다.

Selection 및 Range 가져오기

const selection = document.getSelection();

// Selection.getRangeAt(0) 메소드를 사용해 Range를 얻습니다.
// 다중 선택을 지원하는 경우, 아래와 같이 rangeCount 값을 이용해 Range를 구할 수 있습니다.
for (let i = 0; i < selection.rangeCount; i++) {
  console.log(selection.getRangeAt(i).cloneContents());
}

Selection 조작하기

const selection = document.getSelection();

// selection에 직접 접근
selection.setBaseAndExtent(...from...to...);

// selection의 range를 재설정
selection.removeAllRanges();
selection.addRange(range);

Selection

사용자가 마우스를 통해 드래그하거나, 키보드를 통해 선택한 텍스트의 범위를 나타냅니다. 브라우저에서는 사용자가 선택한 텍스트에 대한 처리를 지원하기 위해 Selection API를 지원하고 있습니다.

Anchor, Focus

Selection은 anchor 값과 focus 값을 가집니다.

  • anchor: 텍스트를 선택한 지점
  • focus: 선택이 종료된 지점

위 두 속성을 보고 알 수 있는 사실이 하나 있는데, Selection은 '방향'을 가질 수 있습니다.

좌측에서 우측으로 마우스 드래그를 한다면 anchor 값보다 focus 값이 클 것입니다. 반대로 우측에서 좌측으로 드래그한다면 anchor 값이 focus 값보다 작을 것입니다.

Selection 객체

interface Selection {
  readonly attribute Node? anchorNode;
  readonly attribute unsigned long anchorOffset;
  readonly attribute Node? focusNode;
  readonly attribute unsigned long focusOffset;
  readonly attribute boolean isCollapsed;
  readonly attribute unsigned long rangeCount;
  readonly attribute DOMString type;
  Range getRangeAt(unsigned long index);
  undefined addRange(Range range);
  undefined removeRange(Range range);
  undefined removeAllRanges();
  undefined empty();
  undefined collapse(Node? node, optional unsigned long offset = 0);
  undefined setPosition(Node? node, optional unsigned long offset = 0);
  undefined collapseToStart();
  undefined collapseToEnd();
  undefined extend(Node node, optional unsigned long offset = 0);
  undefined setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset);
  undefined selectAllChildren(Node node);
  [CEReactions]
  undefined deleteFromDocument();
  boolean containsNode(Node node, optional boolean allowPartialContainment = false);
  stringifier DOMString ();
};

Selection Interface

Selection 객체는 window.getSelection()으로 Selection 객체를 호출해 불러올 수 있습니다. 또한 Selection 객체는 Range 객체의 정보를 갖고 있습니다.

Selection 객체 얻기

Selection 객체를 얻는 방법에는 크게 두 가지가 있습니다.

  1. window 객체에서 getSelection() 메소드를 통해 불러오는 방법
  2. document 객체에서 getSelection() 메소드를 통해 불러오는 방법

위 두 방법 모두 동일한 Selection 객체를 반환하며, Window 객체의 getSelection() 메소드를 사용하는 경우, document 속성에 기반하여 Selection 객체를 반환받습니다.

Selection 객체의 속성

  • selection.anchorNode: 드래그 또는 키보드 이벤트로 선택이 시작된 노드를 반환합니다. 없다면null을 반환합니다.
  • selection.anchorOffset: 선택이 시작된 노드에서 시작된 텍스트의 지작 지점을 의미합니다. 노드의 처음 시작점의 offset은 0입니다.
    "Hello, World!" 문장에서 선택된 문장이 "World!"라면 시작 지점은 7, 끝 지점은 12 입니다.anchorOffset은 7이 됩니다.
  • selection.focusNode: 선택이 종료된 지점에 있는 노드를 반환합니다. 없다면null을 반환합니다.
  • selection.focusOffset: 선택이 종료된 노드에서 텍스트의 종료 지점을 의미합니다. 노드의 처음 시작점의 offset은 0입니다.
    이 외의 규칙은 anchorOffset과 동일합니다.
  • selection.isCollapsed: anchor,focus가 같은 지점에 있는지 확인합니다.
    클릭을 한 경우,anchor와focus가 같은 지점에 존재하며, 입력이 가능한 경우 커서가 깜빡입니다.
  • selection.rangeCount: 현재 브라우저의Context에 존재하는Range의 수를 반환합니다.
  • selection.type
    • Care: collapsed 상태
    • Range: 범위가 선택된 상태
    • None: 아무런 이벤트도 발생되지 않은 경우

Selection 객체의 메소드

  • selection.getRangeAt(index): index에 있는 Range 객체를 반환받습니다.
  • selection.addRange(range): 현재 Selection에 Range를 추가합니다.
  • selection.collapse(node, offset): 넘겨진 노드와 offset으로 Range를 접습니다.
    offset을 지정해 이동시킬 노드가 자식 노드를 갖고 있는 경우, 몇 번째 자식 노드에서 접을지 지정할 수 있습니다.
  • selection.collapseToEnd(): 선택된 range의 끝 지점으로 접습니다.
  • selection.collapseToStart(): 선택된 range의 시작 지점으로 접습니다.
  • selection.containsNode(node,containment): 주어진 노드가 선택된 범위 안에 존재하는지 확인하여 boolean 값으로 반환합니다.
    containment을 지정해 노드의 시작점과 끝점이 선택된 번위와 동일할 때 반활될 방식을 지정할 수 있습니다.
    • true: 노드와 선택된 범위가 동일할 때 포함된 것으로 인식
    • false: 노드와 선택된 범위가 동일할 때 포함되지 않은 것으로 설정
  • selection.deleteFromDocument(): 선택된 범위를 Document 객체에서 제거합니다.
    (선택된 범위를 문서에서 제거합니다.)
  • selection.removeAllRanges(): Selection 객체 안에 있는 모든 Range를 제거하여 아무것도 선택되지 않은 상태로 만듭니다.
  • selection.removeRange(range): 특정 range를 Selection 객체 안에서 제거합니다.
    제거할 Range 객체는 Selection 객체 안에 포함되어 있어야 합니다.
  • selection.extend(node, offset): anchor는 가만히 두고, focus만 주어진 노드와 offset으로 이동시킵니다.
    offset을 지정해 이동시킬 노드가 자식 노드를 갖고 있는 경우, 몇 번째 자식 노드로 focus를 이동시킬 것인지 지정합니다.
  • selection.setBaseAndExtend(anchorNode,anchorOffset,focusNode,focusOffset): 주어진 노드로 선택 범위와 끝 지점을 지정합니다.
    extend는 선택의 끝 지점만 설정할 수 있지만, setBaseAndExtend는 시작 노드와 끝 노드를 모두 지정할 수 있습니다.
  • selection.selectAllChildren(parentNode): 부모 노드를 넘겨주면 부모 노드 아래에 있는 모든 자식 노드가 선택됩니다.
  • selection.setPosition(): selection.collapse() 메소드와 동일합니다.
  • selection.empty(): selection.removeAllRanges() 메소드와 동일합니다.

Selection 객체의 이벤트

  • selectStart: 유저의 드래그나 클릭에 의해 새로운 Range 객체가 연결될 때 발생되는 이벤트
  • selectionchange: 현재 붙어있는 Range 객체가 아닌, 새로운 Range 객체가 붙거나, 현재 Range 객체의 경계선이 변형될 때 발생하는 이벤트

Ragne 객체

Range 객체는 텍스트 노드를 포함한, Selection을 통해 선택된 텍스트의 범위에 대한 정보를 갖고 있습니다.

이 값은 selection.getRangeAt() 메소드를 사용해 얻을 수 있습니다.

일반적으로 하나의 Selection은 보통 하나의 Range를 갖고 있습니다. 하지만, FireFox의 경우, 한 Selection에 여러 개의 Range 객체를 가질 수 있습니다 (FireFox 브라우저는 Ctrl + 마우스 드래그를 통해 여러 개의 Range를 선택할 수 있습니다).

위와 같은 특성으로 인해 FireFox 브라우저를 지원하는 경우, FireFox 브라우저를 대응하는 코드 작성이 필요할 수 있습니다.

Range 객체 범위 생성

Selection에 Range를 추가해 여러 개의 Range를 가질 수 있도록 작성할 수도 있습니다. 이 외에도 독자적인 Range 값을 생성할 수 있습니다.

document.createRange() 메소드를 사용해 새로운 범위를 생성할 수 있습니다.

const range = document.createRange();

Renage 객체의 속성

  • range.startContainer: 범위가 시작하는 부분을 포함하고 있는 노드
  • range.endContainter: 범위가 끝나는 부분을 포함하고 있는 노드
  • range.startOffset: startContainer에서 범위가 시작하는 지점의 offset
    • startContainer가TEXT_NODE라면 문자의 갯수
    • startContainer가ELEMENT_NODE라면 자식 노드의 인덱스
  • range.endOffset: endContainer에서 범위가 끝나는 지점의 offset
    • startOffset과 동일한 규칙이 적용됩니다.
  • range.collapsed: Range의 시작점과 끝점이 같은 위치인지 알 수 있는 boolean 값을 반환합니다.
  • range.commonAncestorContainer: startContainer와 endContainer가 포함된 최상위 노드를 반환합니다.

Range 객체의 메소드

  • range.setStart(refNode, startOffset): 범위의 시작 지점을 refNode, startOffset으로 지정합니다.
  • range.setEnd(refNode, endOffset): 범위의 마지막 지점을 refNode, endOffset으로 지정합니다.
  • range.setStartBefore(refNode): 범위의 시작 지점이 refNode의 앞으로 지정되며, refNode가 첫 번재 노드가 됩니다.
  • range.setStartAfter(refNode): 범위의 시작 지점이 refNode의 다음으로 지정되며, refNode.nextSlbling이 첫 번째 노드가 됩니다.
  • range.setEndBefore(refNode): 범위의 마지막 지점이 refNode의 앞으로 지정되며, refNode.previousSibling이 마지막 노드가 됩니다.
  • range.setEndAfter(refNode): 범위의 마지막 지점이 refNode의 다음으로 지정되며, refNode.previousSibling이 마지막 노드가 됩니다.
  • range.selectNode(refNode): element를 포함한 전체 노드를 선택합니다 (outerHTML).
  • range.selectNodeContents(refNode): element의 하위 노드를 선택합니다 (innerHTML).
  • range.collapse(bool): 선택된 범위를 접습니다.
    • true: 시작점으로 접습니다.
    • false: 끝점으로 접습니다.
  • range.cloneContents(): 범위를 복제한 뒤, 복제한 범위의 DocumentFragment 객체를 반환합니다.
  • range.deleteContents(): 범위를 제거합니다.
  • range.extractContents(): 범위를 삭제한 뒤, 삭제된 범위의 DocumentFragment 객체를 반환합니다.
  • range.insertNode(newNode): 범위의 앞에 newNode를 삽입합니다.
  • range.surroundContents(newNode): 콘텐츠를 newNode로 감쌉니다.
    1. 범위를 추출합니다(extractContents).
    2. newNode를 범위의 위치에 추가합니다(insertNode).
    3. 추출한 콘텐츠를 newNode에 추가합니다(appendChild).
      newNode로 감싸기 위해서는 startContainer, endContainer, commonAncestorContainer 값이 동일해야 합니다.
  • range.compareBoundaryPoints(how, sourceRange): 범위 경계가 겹치는지 확인합니다.
    • [how (비교 방법)]
      • range.START_TO_START (0)
        • range의 시작점과 sourceRange의 시작점을 비교합니다.
      • range.START_TO_END(1)
        • range의 시작점과 sourceRange의 끝점을 비교합니다.
      • range.END_TO_END(2)
        • range의 끝점과 sourceRange의 끝점을 비교합니다.
      • range.END_TO_START(3)
        • range의 끝점과 sourceRange의 시작점을 비교합니다.
    • [반환 값]
      • range의 비교 지점과 sourceRange의 비교 지점을 비교하여 반환합니다.
        • -1: range의 비교 지점이 앞에 있을 때
        • 0: range와 sourceRange의 비교 지점이 일치할 때
        • 1: range의 비교 지점이 뒤에 있을 때
  • range.cloneRange(): 범위를 복제할 수 있습니다.
  • range.detach(): 범위로 할 일이 끝났다면 메모리를 회수해야 합니다.
728x90
728x90