notion 코딩 프로젝트 중, 마우스 드래그로 선택된 텍스트의 스타일을 변경하는 기능을 구현한 방법과 그 과정에서 알게된 내용을 정리해보려 한다.
드래그를 통해 텍스트를 선택할 때 선택된 텍스트를 어떻게 알려줄 수 있을까? 그 답은 Selection 객체이다. Selection 객체는 선택된 텍스트나 그 범위를 반환한다. window.getSelection() 이나 document.getSelection()을 통해 Selection 객체를 얻을 수 있다.
selection = window.getSelection();
텍스트를 선택되었을 때 발생하는 이벤트는 바로 select event이다. 그러나 문제는 select event는 input, textarea element에서만 발생한다는 것이다. 만약, input, texarea외에 element에서 사용하려면, mouseup, mousedown,click 이벤트를 활용할 수 있으며 react를 통해 편집가능한 div element를 사용한다면 react-contentEditable 라이브러리를 이용할 수 있다. 해당 라이브러리에서도 select event를 작동시킬 수 있다.
MouseEvent가 발생했을때, select event도 동시에 작동하는 것을 확인했다. 즉 select event를 넣어준 element에 MouseEvent도 넣어주었다면, 이 둘을 구분하거나 둘 중 하나가 불필요할 경우 작동되지 않게 하는 코드가 필요하다는 것을 의미하기도 한다.
모바일 브라우저의 경우 웹 브라우저와 다른 이벤트를 사용해야한다. 그 이유는 모바일 브라우저의 경우 2)-②에서 언급했듯이 select event는 mouseEvent와 동시에 발생하고 모바일 브라우저에서는 touch start event와 동시에 발생해서 Selection 객체의 anchorOffeset과 focueOffeset이 모두 처음 터치한 부분의 값으로 나와서 실제로 사용자가 선택한 글자의 범위를 알 수 없다.
그래서 ⚠️ 모바일 브라우저에서도 selection을 통한 특정한 기능을 하려면, onSelection 은 오로지 웹 브라우저에서만 사용 가능하도록 해야한다.
모바일 브라우저에서는 document.onselectionchange를 이용해 selection의 변화를 감지 하고, 변화가 있을 시에 selection이 있을 경우 오픈하는 컴포넌트를 마운트 시킨 후, 선택된 글자에 대해 폰트 색변경등의 스타일을 변경할 때 Selection 객체를 가져와서 변경하는 방식으로 진행했다.
document.onselectionchange : 유의미한 Selection 감지
🔽
BlockStyler component monunt : 선택된 글자의 스타일을 변경하는 컴포넌트
🔽
BlockStyler에서 스타일을 변경하는 button 클릭
🔽
document.getSelection 으로 현재 선택된 범위 탐색
🔽
이 후의 프로세스는 웹 브라우저와 동일
Selection.toString()을 통해 화면상에서 선택된 텍스트 값을 불러올 수 있다.
그러나 문제는 말그대로 화면상에서 선택된 텍스크 값만을 불러온다는 것이다.
위의 자료처럼 Text text의 "ext text"를 선택했지만, 서로 다른 코드의 상황일 수 있다는 점이다. 코드2) 상황을 자세히 보면, ext는 span element의 textContent의 일부이고, t는 span element의 다음 node인 text node의 일부이지만, toString()은 이에 대한 표현없이 그저 "ext t"라는 값만을 반환한다, 만약, span element의 스타일은 유지한채, 선택된 텍스트의 스타일을 변경하려 한다면 toString()을 사용한 텍스트 스타일 변경은 한계가 있다.
그래서 내가 선택한 방법은 Selection의 properties 인 anchorNode,anchorOffset, focusNode, focusOffset을 이용하는 것이다.
anchorNode: select event가 시작된 node
anchorOffset: anchorNode에서 select event가 시작된 지점
focusNode: select event가 끝난 node
focusOffset: focusNode에서 select event가 끝난 지점
anchorNode과 focusNode로 select가 일어난 node를 알아내고, anchorOffset과focusOffset으로 anchorNode과 focusNode에서 선택된 텍스트와 그렇지 않은 텍스트를 구별해 낼 수 있다.
여기서 주의할 점은 anchorOffset과 focusOffset이다.
anchorOffset 과 focusOffset기 곧 해당 노드에서 선택된 첫번째 글자, 마지막 글자의 index를 의미가는 것을 아니며 마우스 드래그의 방향에 따라 변경된다는 것이다.
anchorNode: Text
anchorOffset: 1
focusNode: text
focusOffset: 1
선택된 텍스트에 스타일을 지정해 element의 innerHtml을 변경한 모습과 코드는 다음과 같다.
text 일부에 스타일이 지정된 모습
text 일부에 스타일을 지정한 코드
<div
class="contentEditable"
contenteditable="true">
header1
<span class="color color_green">
header
<span class=" color color_blue">
1
</span><
/span>
</div>
select을 이용해 element을 변경하는 방법은 select로 인해 data변경이 필요한 node를 직접 변경하거나, element의 innerHtml 자체를 변경하는 방법이다.
내가 선택한 방법은 element의 innerHtml 전체를 변경하는 것이다.
이는 element의 innerHtml에서 선택되기 전의 부분, 선택된 부분, 선택된 부분의 뒷 부분으로 나누어서 새로운 innerHtml을 생성하고 적용하는 방법이다. 이 방법은 element안에 내용이 길어질 경우 속도의 저하가 일어날 수 있지만 text가 선택된 다양한 경우를 커버할 수 있고 그로 인해 오류가 발생할 수 있는 가능성도 낮다는 장점이 있다.
텍스트 선택으로 내용이 변경된 node 수정은 수정될 node만 변경한 다는 점에서 장점이 있지만 select가 일어나는 다양한 경우의 수들을 모두 일일이 설정해야한다는 단점이 있다. 예를 들어 anchorNode과 focusNode는 모두 text node 라 해당 node의 parentNode를 변경해야 하는데, anchorNode와 focusNode의 paretNode가 같은 경우와 다른 경우 따로 설정해주어야 하고, parentNode가 다른 node의 parentNode의 panretNode인 경우도 생각해야한다. 이외에도 더 많은 select가 일어날 수 있는 상황을 예상하고 그에 대응하는 것은 한계가 있다.
selection을 공부하면서 얻은 것은 이벤트를 구현하는 것에서만 끝나는게 아니라 그 이벤트에 대한 세부사항도 찾아봐야한다는 것이다. 처음에 selection.toString()만으로 이미 스타일이 지정된 텍스트의 스타일을 변경하거나 지정된 스타일을 유치한 채 새로운 스타일을 지정해야하는 것을 구현하려 할때 막막했었다. 이는 selection을 살펴보지 않고 선택된 글자를 가져오는 방법인 selection.toString()을 통해서 원하는 기능을 구현하는 것에만 초점을 두었기 때문이다.