/
/
    ⚛️ React

    react에서 더보기 기능 구현하기

    React와 CSS를 활용해 말줄임 처리와 더보기 기능을 구현하는 방법을 단계별로 설명하며, 애니메이션까지 적용해 사용자 경험을 향상시키는 과정을 다룹니다.
    line-clmapellipsisCSSReact
    m
    morethanmin
    2024.12.21
    ·
    5 min read

    사용자 인터페이스(UI)를 디자인할 때 긴 텍스트를 제한된 공간에 표시해야 하는 경우가 종종 있습니다. 이러한 상황에서는 텍스트를 말줄임 처리하거나 "더보기" 기능을 추가하여 사용자 경험을 개선할 수 있습니다. 이 글에서는 React를 활용해 텍스트를 말줄임 처리하고, 더보기 기능을 구현하는 방법을 단계별로 알아보겠습니다.

    텍스트 말줄임 처리하기

    2602

    먼저 텍스트를 특정 줄까지만 표시하고 나머지는 숨기는 방법을 살펴보겠습니다. 이를 위해 CSS의 line-clamp 속성을 활용합니다. 아래는 lineClamp 속성을 받아 특정 줄까지만 표시하는 EllipsisTypography 컴포넌트입니다.

    import type { ComponentProps, FC, PropsWithChildren } from "react";
    
    import { css } from "@emotion/react";
    import { Typography } from "ui";
    
    const wrapperStyle = css`
      position: relative;
    
      display: -webkit-inline-box;
    
      overflow: hidden;
    
      transition: max-height 0.3s ease;
    
      -webkit-box-orient: vertical;
    `;
    
    type EllipsisTypographyProps = PropsWithChildren<{
      variant: ComponentProps<typeof Typography>["variant"];
      lineClamp: number;
    }>;
    
    const EllipsisTypography: FC<EllipsisTypographyProps> = ({ lineClamp, variant, children }) => {
      return (
        <Typography
          variant={variant}
          css={[
            wrapperStyle,
            {
              WebkitLineClamp: lineClamp,
            },
          ]}
        >
          {children}
        </Typography>
      );
    };
    
    export default EllipsisTypography;

    더보기 기능 추가하기

    이제 말줄임 처리된 텍스트에 더보기 기능을 추가해봅시다. 텍스트의 말줄임 여부는 clientHeight와 scrollHeight를 비교하여 확인할 수 있습니다. 컴포넌트 이름은 역할에 맞게 ExpandableTypography로 변경했습니다.

    import type { ComponentProps, FC, PropsWithChildren, ReactNode } from "react";
    import { useEffect, useRef, useState } from "react";
    
    import { css } from "@emotion/react";
    import { Typography } from "@PRNDcompany/revolt-ui";
    
    const wrapperStyle = css`
      position: relative;
    
      display: -webkit-inline-box;
    
      overflow: hidden;
    
    
      -webkit-box-orient: vertical;
    `;
    
    type ExpandableContentProps = PropsWithChildren<{
      variant: ComponentProps<typeof Typography>["variant"];
      lineClamp: number;
      expendButton?: ReactNode;
    }>;
    
    const ExpandableTypography: FC<ExpandableContentProps> = ({ lineClamp, children, expendButton, ...props }) => {
      const ref = useRef<HTMLSpanElement>(null);
      const [isExpanded, setIsExpanded] = useState(false);
    
      useEffect(() => {
        if (!ref.current) return;
    
        setIsExpanded(ref.current.scrollHeight <= ref.current.clientHeight);
      }, []);
    
      const handleClick = () => {
        if (!ref.current) return;
    
        setIsExpanded(true);
      };
    
      return (
        <Typography
          {...props}
          ref={ref}
          onClick={handleClick}
          css={[
            wrapperStyle,
            {
              cursor: isExpanded ? "auto" : "pointer",
              WebkitLineClamp: isExpanded ? "auto" : lineClamp,
            },
          ]}
        >
          {children}
    
          {!isExpanded && expendButton && <div css={{ position: "absolute", right: 0, bottom: 0 }}>{expendButton}</div>}
        </Typography>
      );
    };
    
    export default ExpandableTypography;
    
    

    애니메이션 적용하기

    더보기 기능에 애니메이션을 추가해 사용자 경험을 개선할 수 있습니다. WebkitLineClamp는 애니메이션을 지원하지 않으므로 maxHeight 값을 사용하여 애니메이션을 구현합니다.

    import type { ComponentProps, FC, PropsWithChildren, ReactNode } from "react";
    import { useEffect, useRef, useState } from "react";
    
    import { css } from "@emotion/react";
    import { Typography } from "@PRNDcompany/revolt-ui";
    
    const wrapperStyle = css`
      position: relative;
    
      display: -webkit-inline-box;
    
      overflow: hidden;
    
      transition: max-height 0.3s ease;
    
      -webkit-box-orient: vertical;
    `;
    
    type ExpandableContentProps = PropsWithChildren<{
      variant: ComponentProps<typeof Typography>["variant"];
      lineClamp: number;
      expendButton?: ReactNode;
    }>;
    
    const ExpandableTypography: FC<ExpandableContentProps> = ({ lineClamp, children, expendButton, ...props }) => {
      const ref = useRef<HTMLSpanElement>(null);
      const [isExpanded, setIsExpanded] = useState(false);
    
      useEffect(() => {
        if (!ref.current) return;
    
        setIsExpanded(ref.current.scrollHeight <= ref.current.clientHeight);
        ref.current.style.maxHeight = `${ref.current.getBoundingClientRect().height}px`;
      }, []);
    
      const handleClick = () => {
        if (!ref.current) return;
    
        setIsExpanded(true);
        ref.current.style.maxHeight = `${ref.current.scrollHeight}px`;
      };
    
      return (
        <Typography
          {...props}
          ref={ref}
          onClick={handleClick}
          css={[
            wrapperStyle,
            {
              cursor: isExpanded ? "auto" : "pointer",
              WebkitLineClamp: isExpanded ? "auto" : lineClamp,
            },
          ]}
        >
          {children}
          {!isExpanded && expendButton && <div css={{ position: "absolute", right: 0, bottom: 0 }}>{expendButton}</div>}
        </Typography>
      );
    };
    
    export default ExpandableTypography;
    

    결과

    2601

    이제 텍스트를 말줄임 처리하고, 더보기 기능과 애니메이션까지 적용할 수 있게 되었습니다. 이를 활용해 사용자 경험을 개선하고 다양한 UI 요구사항을 만족시킬 수 있습니다.

    혹시 더 좋은 구현 방안이 있다면 의견 부탁드립니다!







    - 컬렉션 아티클