avatar
morethan-log

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

React와 CSS를 활용해 말줄임 처리와 더보기 기능을 구현하는 방법을 단계별로 설명하며, 애니메이션까지 적용해 사용자 경험을 향상시키는 과정을 다룹니다.
line-clmapellipsisCSSReact
2 days ago
·
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;

더보기 기능 추가하기

이제 말줄임 처리된 텍스트에 더보기 기능을 추가해봅시다. 텍스트의 말줄임 여부는 clientHeightscrollHeight를 비교하여 확인할 수 있습니다. 컴포넌트 이름은 역할에 맞게 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 요구사항을 만족시킬 수 있습니다.

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


- 컬렉션 아티클






몰댄민입니다