ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 자세히 보기 구현
    javascript 2023. 12. 28. 07:26

    유튜버 노마드코더 영상 댓글

    유튜브와 같은 사이트에서 작성된 게시글이나 댓글을 보면 글자수가 길어지면,

    글자를 자르고 자세히 보기 버튼이 활성화 되어 버튼을 누른 후에 모든 글을 볼 수 있는 기능이 있습니다.

    해당 기능을 리액트에서 구현해 보았습니다.

     

     

    먼저 생각해야 할 것은 글자를 자르는 기준을 생각해야 합니다.

    단순히 일정 글자수가 초과 된 경우 자르게 되면

     

     

    위와 같이 글자수는 많지만 3줄밖에 되지않아 글자를 자르지 않아도 되는 경우에도 글자를 잘라버리는 경우가 발생합니다.

     

    따라서 글자수가 아닌 해당 게시글의 라인수를 기준으로 잘라야 합니다.

     

    그렇다면 개행문자(\n)의 개수로 확인하면 되지않을까? 라는 생각을 할 수 있지만

    개행문자를 사용하지 않더라도 글자가 화면의 끝에 다다르면 자동으로 다음줄로 넘어가지기 때문에 라인수를 정확히 확인하기 어렵습니다.

     

    따라서 저는 화면의 높이를 기준으로 라인수를 체크하는 방법을 사용했습니다.

     

    좌: scroll Height, 우:client height, offset height

     

    특정 DOM 요소의 높이를 구하기 위해서 3가지 방법을 사용합니다.

    scrollHeight: 전체 컨텐츠의 높이(보이지 않는 부분까지)

    clientHeight: 보이는 부분의 컨텐츠 높이

    offsetHeight: clientHeight + 기타요소( 패딩, 보더, 스크롤바)

     

    저는 여기서 scrollHeight와 clientHeight를 사용하였습니다.

     

     

     

     

    자세히보기 출력 기준 구하는 절차

     

    1. 글자 라인 수로 자르기(tailwindCSS 사용)

     

    왼쪽 CSS를 사용하면 글자가 아무리 길어도 4줄까지 밖에 표시되지 않으며 오른쪽과 같이 ...으로 표시됩니다.

     

    2.  scrollHeight와 clientHeight 높이 구하기

    export default MoreText(){
    
        const contentRef = useRef<HTMLDivElement>(null);
        //...
        const current = contentRef.current;
        const clientHeight = current?.clientHeight;
        const scrollHeight = current?.scrollHeight;
    
        //...
    
        return <div ref={contentRef}>텍스트</div>
    }

    텍스트가 표시될 요소를 useRef로 접근후 clientheight와 scrollHeigt를 구합니다.

     

    3.  scrollHeight와 clientHeight 비교

    만약 scrollHeight와 clientHeight가 같지 않다면 CSS로 인해 텍스트가 4줄이상 초과되어 잘렸다는 의미이며, 

    같다면 텍스트가 잘리지 않고 모두 출력되고 있다는 것을 의미합니다.

     

    4. 초과한 텍스트인지 아닌지 상태 저장

    const [hasMore, setHasMore] = useState(false);
    
    //...
    
    if (clientHeight === scrollHeight) setHasMore(false); //초과x
    else setHasMore(true); //초과됨
    
    //...

    위와 같은 코드를 추가하여 비교한 결과를 State를 추가하여 랜더링 될 수 있도록 합니다.

     

     

     

    5. 자세히보기 핸들러 추가

    const [isClickMore, setIsClickMore] = useState(false);
    
    //...
    
    const handleMore = () => {
        setIsClickMore(true);
    };
    const handleSimple = () => {
        setIsClickMore(false);
    };

    자세히보기를 눌렀을 때 작동하는 상태와 핸들러입니다

     

    6. 결합 및 완성된 코드와 요약

    export default MoreText(){
      const [hasMore, setHasMore] = useState(false);
      const [isClickMore, setIsClickMore] = useState(false);
    
      useEffect(() => {
        const current = ref.current;
        const clientHeight = current?.clientHeight;
        const scrollHeight = current?.scrollHeight;
    
        if (!clientHeight || !scrollHeight) return;
    
        if (clientHeight === scrollHeight) setHasMore(false); //초과x
        else setHasMore(true); //초과됨
      }, [ref, hasMore]);
    
      const handleMore = () => {
        setIsClickMore(true);
        // setHasMore(true);
      };
      const handleSimple = () => {
        setIsClickMore(false);
        // setHasMore(false);
      };
    
      return (
        <>
          <div
            ref={contentRef}
            className={`line-clamp-4 ${isClickMore && "line-clamp-none"}`}
          >
            {children}
          </div>
          {hasMore && !isClickMore && (
            <button onClick={handleMore} className="text-start text-black/40">
              자세히 보기
            </button>
          )}
          {hasMore && isClickMore && (
            <button onClick={handleSimple} className="text-start text-black/40">
              간략히보기
            </button>
          )}
        </>
      );
    }

    1. 표시될 텍스트를 4줄로 자름 -> 만약 4줄 이하라면 client와 scroll 높이가 같음, 이상이라면 client와 scroll 높이가 다름

    2. useEffect에서 택스트가 표시될 div요소의 높이값을 구해 client와 scroll 높이를 비교 -> hasMore에 true혹은 false 저장

    3. hasMore가 true이면서

      - 자세히보기를 클릭하지 않은경우

         # 자세히 보기 버튼 표시

         # 4줄 자르기 CSS 유지

      - 자세히보기를 클릭한경우

         # 간략히 보기 버튼 표시

         # line-clamp-none으로 4줄 자르기 CSS 제거

     

     

     

    7. 사용

    <MoreText>택스트내용입력...</MoreText>

    실제사용된 이미지

     

Designed by Tistory.