Range 객체를 통해 반응형 댓글 구현하기

terry yoon·2022년 5월 4일
1
post-thumbnail

글의 목적

회사에서 개발 목적으로 댓글 기능 일부를 수정할 일이 발생하였다. 댓글 기능 중 제한된 글자 수와 라인 수를 넘어가는 경우, ellipsis 처리를 하고 더보기를 클릭할 경우 전체 내용을 보여주는 기능에서 에러가 발생하였다.

예를 들어 글자 수가 maximum 보다 넘지 않지만, 기기에 따라 넓이가 달라 text의 라인 수가 5줄을 넘어가는 경우 ellipsis 처리가 되어 있었다. 하지만 기존 코드에서 더보기 버튼은 text(string)의 length를 기준으로 활성화 여부를 판단하도록 되어 있어, 현재 렌더링 된 Text node의 높이를 계산하여 라인 수를 판별할 필요가 있었다.

위의 사진을 보면 글자 수가 120자를 넘지 않아도, 기기의 너비에 따라 5줄을 넘어가기 때문에 실제 내용이 짤려서 보이는 현상이 발생한다.

이를 해결할 방법으로 현재 span 태그 하위의 text Node의 너비를 구하는 방법을 생각하였다. stackoverflow 에 누군가 친절히 해당 로직을 올려두어 해당 로직을 적용하였고, 해당 로직을 보며 Range 객체에 대해 공부한 내용을 정리하며 공유한다

해결 방법


function getTextNodeHeight(textNode: Node) {
  let height = 0;
  if (document.createRange) {
    const range = document.createRange();
    range.selectNodeContents(textNode);
    if (range.getBoundingClientRect) {
      const rect = range.getBoundingClientRect();
      if (rect) {
        height = rect.bottom - rect.top;
      }
    }
  }
  return height;
}

Range 객체 (🔗 MDN)

The Range interface represents a fragment of a document that can contain nodes and parts of text nodes. (mdn)

즉, Range 객체란 document의 일부분(fragment)을 의미한다. 다만, Range 인터페이스에서 제공하는 메소드를 이용해, document 내의 특정 부분을 지정하여 가져올 수 있다는 장점이 존재한다.

A range can be created by using the Document.createRange() method.

위의 코드에서 볼 수 있듯이 Range 객체는 Document 에서 제공하는 createRange 함수로 생성가능하다.

Range.selectNodeContents(Node) (🔗 MDN)

The Range.selectNodeContents() method sets the Range to contain the contents of a Node.

해당 메서드는 인수로 전달받은 referenceNode의 contents를 Range 객체가 포함(contain)하도록 지정(set)하는 함수이다.

The startOffset is 0, and the endOffset is the number of child Nodes or number of characters contained in the reference node.

이렇게 되면 Range 객체의 startOffset과 endOffset이 reference Node 의 child 수 또는 글자 수(number of characters)로 지정된다. startOffset MDN에 따르면 startOffset은 Range가 시작되는 startContainer의 위치를 나타낸다. 만약 startContainer가 Text, Comment 등과 같은 타입의 노드라면 해당 노드의 문자 수가 offset으로 지정되고, 그 이외의 경우 reference Node의 child 노드 개수로 지정된다.

해당 Selection 객체를 이용해 Range 객체 내부의 contents를 선택할 수도 있다.

const p = document.getElementById('paragraph');
const selection = window.getSelection();
const range = document.createRange();

range.selectNodeContents(p);
selection.addRange(range); // p element 안의 content 선택

Range.getBoundingClientRect() (🔗 MDN)

The Element.getBoundingClientRect() method returns a DOMRect object providing information about the size of an element and its position relative to the viewport.

해당 메서드는 viewport를 기준으로 요소의 size와 position 정보를 담고 있는 DOMRect 객체를 반환한다. DOMRect 객체는 css의 box-model을 이해하면 쉽게 이해할 수 있다. HTML에서는 모든 요소를 box 형태로 표현하는데, DOMRect 역시 box 형태로 표현된 element의 좌표값과 너비/높이 값을 가진 객체를 의미한다.

따라서 해당 객체의 top과 bottom 정보를 이용해, 우리가 원하는 Range 객체의 높이를 구할 수 있게 된다.

마무리

Range 인터페이스에 대해 막연히 알고 있던 것을 정리하였다. 앞으로 인터페이스를 사용하면서, 모르는 것을 더 정리하여 유용하게 사용할 수 있길 기대한다

profile
배운 것을 기록하는 FrontEnd Junior 입니다

0개의 댓글