최근 모 기업의 과제를 진행했는데, 평소에는 당연시 여기는 기능을 구현하는 것이었습니다.
이 기능을 구현하면서 Selection API와 Range에 대해 처음 알게 되었고, 당연히 과제를 미흡하게 완성했습니다.
최근 회사에 입사지원서를 넣으면서 자기소개로 "기본에 충실하면서 ~"라는 문구를 넣었는데, 과제를 진행해 보니 기본에 충실하지 못하다는 것을 깨달았습니다.
기본에 충실한 개발자가 되도록하는 노력의 일환으로 Selection API와 Range에 대해 조사해 봤습니다.
Selection API를 사용하려면 우선 Range 객체에 대해 알아야합니다.
Range
Range는 사전적으로 시작점과 끝점의 쌍(Pair)을 의미합니다.
Range 객체는 Selection의 범위를 관리하는 객체입니다.
Range 객체에서 인자로 넘기는 노드가 element node이면 offset은 자식 노드의 번호가 되고,
인자로 노드가 text node이면 offset은 텍스트 내의 위치가 됩니다.
예시를 보면서 설명드리겠습니다.
<p>My name is <bold>Bo Hyun</bold>, a Frontend developer.</p>
Range 객체에서 부모 노드를 "p"로 해서 범위를 잡아보겠습니다.
See the Pen Untitled by 김보현 (@xkjqtncn-the-vuer) on CodePen.
부모의 노드가 element node이니 offset은 자식 노드의 번호가 되어 첫 번째랑 두 번째 노드가 선택됩니다.
1. My name is
2. <bold>Bo Hyun</bold>
3. a Frontend developer.
중 1번과 2번 노드가 범위에 잡힙니다.
다음은 부모 노드를 "My name is"(text node)로 선택하겠습니다.
See the Pen Range, select node by 김보현 (@xkjqtncn-the-vuer) on CodePen.
이 때는 "My na"이 범위로 잡히는 것을 볼 수 있습니다.
Range Methods
Range 객체에는 범위를 조작할 수 있는 다양한 메서드를 제공합니다.
Range의 시작 부분 지정
setStart(node, offset) - 시작 지점을 node의 offset으로 지정합니다.
setStartBefore(node) - 시작 지점을 node 이전으로 지정합니다.
setStartAfter(node) - 시작 지점을 node 이후로 지정합니다.
Range의 종료 부분 지정
setEnd(node, offset) - 종료 지점을 node의 offset으로 지정합니다.
setEndBefore(node) - 종료 지점을 node 이전으로 지정합니다.
setEndAfter(node) - 종료 지점을 node 이후로 지정합니다.
이 외
selectNode(node) - node 전체를 범위로 지정합니다.
selectNodeContents(node) - node의 contents를 범위로 지정합니다.
collapse(toStart) - toStart가 true 이면 end = start가 되고 false이면 start = end가 됩니다.
cloneRange - 시작지점과 종료지점이 같은 새로운 range를 생성합니다.
Range의 Contents 조작
deleteContents() - range의 contents를 삭제합니다.
extractContents() - range의 contents를 삭제하고 삭제한 contents를 DocumentFragment에 담아서 반환합니다.
cloneContents() - range의 contents를 복사해서 DocumentFragment에 담아서 반환합니다.
insertNode(node) - node를 range의 시작지점에 삽입합니다.
surroundContents(node) - range의 contents를 node로 감쌉니다. 단, range안의 모든 element는 시작 및 종료 태그를 포함하고 있어야 합니다.
Selection
selection 객체는 window.getSelection 혹은 document.getSelection으로 가져올 수 있습니다.
selection은 0개 혹은 그 이상의 range를 가질 수 있습니다.
대부분의 브라우저들은 최대 1개의 range를 가질 수 있지만 파이어폭스는 1개 이상의 range를 가질 수 있습니다.
range와 비슷하게 selection도 시작에 해당하는 "anchor"와 끝에 해당하는 "end"가 존재합니다.
Selection Properties
Selection은 다음과 같은 프로퍼티를 갖습니다.
anchorNode - selection이 시작되는 node
anchorOffset - selection이 시작되는 node의 offset
focusNode - selection이 끝나는 node
focusOffset - selection이 끝나는 node의 offset
isCollapsed - true이면 selection에 아무것도 select가 안된 것입니다.
rangeCount - range의 갯수, 파이어폭스를 제외한 브라우저는 최대 1개의 range를 가질 수 있습니다.
❗️문서상에서 Selection의 종료 지점이 시작지점보다 먼저 올 수 있습니다.
Selection의 시작 지점과 종료 지점은 사용자가 select 하는 방법에 따라 다르게 지정됩니다.
사용자가 왼쪽 -> 오른쪽으로 선택 : 문서상 anchor(시작지점)이 focus(종료지점)보다 먼저 옵니다.
사용자가 오른쪽 -> 왼쪽으로 선택 : 문서상 focus(종료지점)이 anchor(시작지점)보다 먼저 옵니다.
Selection Methods
Selection은 range를 추가/제거 할 수 있는 다양한 메서드를 제공합니다.
Range 조회
getRangeAt(i) - i번째 인덱스의 range를 가져옵니다. 파이어폭스를 제외한 브라우저에서는 0만 사용됩니다.
Range 추가
addRange(range) - range를 selection에 추가합니다. selection이 이미 range를 가지고 있으면, firefox를 제외한 브라우저에서는 호출이 무시됩니다.
Range 삭제
removeRange(range) - selection에서 range를 제거합니다.
removeAllRange() - 모든 range를 제거합니다.
empty() - removeAllRange 메서드의 별칭입니다.
Selection은 range를 변경할 수 있는 메서드도 제공합니다.
collapse(node, offset) - selected range를 특정 노드의 특정 위치로 대체합니다.
setPosition(node, offset) - collapse 메서드의 별칭입니다.
collapseToStart() - selected range를 시작 지점으로 축소합니다.
collapseToEnd() - selected range를 종료 지점으로 축소합니다.
extend(node, offset) - selected range의 종료 지점을 특정 노드의 위치로 지정합니다.
setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset) - range를 주어진 anchorNode/anchorOffset, focusNode/focusOffset으로 바꿉니다. 사이의 모든 contents는 select 됩니다.
selectAllChildren(node) - node의 모든 children을 선택합니다.
deleteFromDocument() - 문서에서 selected된 contents를 삭제합니다.
containsNode(node, allowPartialContainment = false) - selection이 node를 포함하고 있는지 확인합니다.
Selection Events
Selection은 다음과 같은 event로 추적할 수 있습니다.
element.onselectstart - element에서 selection이 시작되면 발생합니다.
document.onselectionchange - selection이 변화하면 이벤트를 호출합니다. 단, document에서만 이벤트 핸들러를 설정 할 수 있습니다.
See the Pen Range, select text by 김보현 (@xkjqtncn-the-vuer) on CodePen.
Selection에서 텍스트는 document.getSelection.toString()으로 가져올 수 있고,
node는 document.getSelection.getRangeAt(i).cloneContents()로 가져올 수 있습니다.
Form에서의 Selection
input이나 textarea같은 element에서는 Selection 혹은 Range 객체를 사용하지 않고도 selection을 조작할 수 있습니다.
추가적으로, input의 값은 순수한 텍스트이기 때문에 조작 및 참조가 훨신 간단합니다.
Properties
input.selectionStart - selection이 시작한 위치
input.selectionEnd - selection이 끝난 위치
input.selectionDirection - selection의 방향 (foward, backward, none)
Events
input.onselect - 무언가가 select되었을 때 이벤트를 호출합니다.
Methods
input.select() - input혹은 textarea같은 text control의 모든 것을 select 합니다.
input.setSelectionRange(start, end, [direction]) - start에서 end까지 select 합니다.
input.setRangeText(replacement, [start], [end], [selectionMode]) - start에서 end까지 값을 replacement로 대체합니다.
selectionMode에는 4가지 값이 있습니다.
"select" - 새로 삽입한 텍스트가 select 됩니다.
"start" - 새로 삽입한 텍스트의 앞으로 selection range가 변경 됩니다.
"end" - 새로 삽입한 텍스트의 뒤로 selection range가 변경 됩니다.
"preserve" - selection을 유지합니다, 기본값 입니다.
맷음말
notion이나 conflunce같은 WYSIWYYG 에디터를 만들 때 이를 사용해서 구현한다고 합니다.
프론트엔드 개발자로서 Selection을 아는것과 모르는 것은 구현능력등에 큰 차이가 있을 것 같습니다.
참조 및 인용
https://ko.javascript.info/selection-range
https://developer.mozilla.org/en-US/docs/Web/API/Selection
'Javascript' 카테고리의 다른 글
[JavaScript] JIT Pipeline (0) | 2025.01.14 |
---|