본문 바로가기

프로젝트/나무(나누고 나눔받는 무한 지식 품앗이)

[React] 텍스트가 넘칠 때 "..." 출력하기

반응형

메인 페이지 캐러셀 컴포넌트에서 게시글의 제목 / 내용 / 게시 날짜 및 시간을 보여준다.

만약 게시글 내용을 가공(?) 없이 그대로 출력되게 둔다면 다른 아이템의 자리까지 침범하게 된다. 그러므로 게시글의 내용을 미리 보기 할 수 있도록 한 줄 정도만 출력되도록 하고 추가적인 내용이 있다는 의미인 '...'을 붙여줘 보자.

이렇게 만들어 줄 거다!


먼저 미리 보기 텍스트를 한 줄 정도 길이로 출력하기 위해 줄 바꿈을 없애준다.

const mergedContent = content.replace(/\n/g, '');

게시글 내용의 html 태그를 없애준 후, 새롭게 만든 div 태그의 값으로 가공한 텍스트를 집어넣는다.

  const stripHTMLTags = (html) => {
    const tmp = document.createElement('div');
    tmp.innerHTML = html;

    return tmp.textContent || tmp.innerText || '';
  };
  
  const sanitizedContent = stripHTMLTags(mergedContent);

그다음, 내용의 길이가 설정한 미리 보기 최대 글자 수를 넘으면 최대 글자수만큼 잘라주고,... 을 붙여준다.

const truncateContent = (text, maxLength) => {
    if (text.length > maxLength) {
      return `${text.slice(0, maxLength)}...`;
    }

    return text;
  };
  
const truncatedContent = truncateContent(sanitizedContent, 70);

전체 코드는 다음과 같다.

const truncateContent = (text, maxLength) => {
    if (text.length > maxLength) {
      return `${text.slice(0, maxLength)}...`;
    }

    return text;
  };

  const stripHTMLTags = (html) => {
    const tmp = document.createElement('div');
    tmp.innerHTML = html;

    return tmp.textContent || tmp.innerText || '';
  };

  const mergedContent = content.replace(/\n/g, '');
  const sanitizedContent = stripHTMLTags(mergedContent);
  const truncatedContent = truncateContent(sanitizedContent, 70);

return 문 안에 다음과 같이 써주면 완성이다.

<Content
     style={{
          color: isHovered ? '#555555' : '#3f3f3f'
     }}
       dangerouslySetInnerHTML={{
          __html: DOMPurify.sanitize(truncatedContent)
       }}
></Content>

 

+ 이렇게 했더니 vw가 줄어들어도 최대 출력 글자 수가 70 자라 텍스트가 부모 컴포넌트를 삐져나와서 출력되는 현상을 발견했다.

결국 부모 컴포넌트에 width를 설정하는 등, css를 수정하여 해결하였다.

import React, { useState, useCallback, useRef, useEffect } from 'react';
import styled, { keyframes } from 'styled-components';
import DOMPurify from 'isomorphic-dompurify';
import { useNavigate } from 'react-router-dom';

const ItemWrapper = styled.article`
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  margin: 10px auto 0px auto;
  padding: 20px 20px 10px 20px;
  border-radius: 20px;
  background-color: #ffffff;
  width: 92%;
  height: calc(10%);
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    transform: scale(1.01);
    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
  }
`;

const fadeIn = keyframes`
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
`;

const AnimatedCarouselItem = styled.div`
  animation: ${fadeIn} 0.5s ease;
  width: 100%;
`;

const Title = styled.div`
  font-size: 14px;
  font-weight: bold;
  transition: color 0.3s ease;
  white-space: nowrap;
  overflow: hidden;
  width: 95%;

  @media (min-width: 1024px) {
    font-size: 16px;
  }

  @media (min-width: 1440px) {
    font-size: 18px;
  }
`;

const Content = styled.div`
  font-size: 12px;
  font-weight: 600;
  transition: color 0.3s ease;
  margin: 5px 0px 0px 0px;
  width: calc(95%);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;

  @media (min-width: 1024px) {
    font-size: 14px;
  }

  @media (min-width: 1440px) {
    font-size: 16px;
  }
`;

const Date = styled.span`
  font-size: 10px;
  font-weight: 300;
  align-self: flex-end;

  @media (min-width: 1024px) {
    font-size: 12px;
  }

  @media (min-width: 1440px) {
    font-size: 14px;
  }
`;

const CarouselItem = ({ title, content, createdAt, id }) => {
  const [isHovered, setIsHovered] = useState(false);
  const [maxCharacters, setMaxCharacters] = useState(62);

  const itemWrapperRef = useRef(null);

  const navigate = useNavigate();

  const formattedDate = new window.Date(createdAt.seconds * 1000);

  const handleMouse = useCallback(() => {
    setIsHovered(!isHovered);
  }, [isHovered]);

  const handleNavigate = useCallback(() => {
    navigate(`/posts/${id}`);
  }, [id]);

  const stripHTMLTags = (html) => {
    const tmp = document.createElement('div');
    tmp.innerHTML = html;

    return tmp.textContent || tmp.innerText || '';
  };

  const mergedContent = content.replace(/\n/g, '');
  const sanitizedContent = stripHTMLTags(mergedContent);

  const options = {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    hour12: true
  };

  return (
    <ItemWrapper
      onMouseEnter={handleMouse}
      onMouseLeave={handleMouse}
      style={{
        backgroundColor: isHovered ? '#f8f8f8' : '#ffffff'
      }}
      onClick={handleNavigate}
    >
      <AnimatedCarouselItem>
        <Title
          style={{
            color: isHovered ? '#9eb23b' : '#3f3f3f'
          }}
        >
          {title}
        </Title>
        {sanitizedContent && (
          <Content
            style={{
              color: isHovered ? '#555555' : '#3f3f3f'
            }}
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(sanitizedContent)
            }}
          ></Content>
        )}
        {/* {truncatedContent && (
          <Content
            style={{
              color: isHovered ? '#555555' : '#3f3f3f'
            }}
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(truncatedContent)
            }}
          ></Content>
        )} */}
      </AnimatedCarouselItem>
      <Date>{formattedDate.toLocaleString('ko-KR', options)}</Date>
    </ItemWrapper>
  );
};

export default CarouselItem;

 

참고한 블로그)

https://devbirdfeet.tistory.com/140

 

CSS - 텍스트가 넘칠때 생략하기 (말줄임)

Updated 08/01/22 css 를 사용하다 보면 은근히 텍스트를 생략해야 할 때가 많다. 지난번에도 UI 작업하다가 버튼 안의 텍스트가 너무 길어 버튼 영역이 망가져 버렸다. 이럴때 너무 텍스트가 길면 ...

devbirdfeet.tistory.com

 

반응형