[React]テキストをDOM領域に合わせてトリミングし、3点リーダーを表示する

Reactでテキストを特定サイズでトリミングして、3点リーダーを表示する要件があったので実装した。

目次

テキストをDOM領域でトリミングして、3点リーダーを表示

Reactのコンポーネントとして実装をする。

変数の定義

トリミングするテキスト状態管理するuseStateと、対象のDOM領域を管理するuseRefを定義します。

  const containerRef = useRef<HTMLDivElement>(null)
  const [text, setText] = useState("");
  // ユーザーインプットによって可変する文字列。本来はコンポーネントから渡してもらうとよい。
  const title = "text_title";

テキストをトリミングする関数

textTrim関数を実装します。

呼び出されたタイミングでuseRefしたDOM横幅と、対象テキストの文字幅を取得しています。
その幅を比較して、DOM横幅を超えている場合は、対象テキストをトリミングして、最後に3点リーダー文字列を追加するようにしています。

  // テキストをコンテナサイズでトリミングする関数
  const textTrim = useCallback(() => {
    if (!containerRef.current) return;

    // テキストを収めるDOM横幅
    const maxTitleWidth = containerRef.current.getBoundingClientRect().width;

    // テキスト文字列の幅
    const textCtx = document.createElement("canvas").getContext("2d");
    textCtx!.font = '16px "arial,sans-serif"';
    let textWidth = textCtx!.measureText(title).width;

    // 幅が指定した最大幅を超えている場合、文字列を切り詰める
    if (textWidth > maxTitleWidth) {
      let truncatedText = title;
      while (textWidth > maxTitleWidth && truncatedText.length > 0) {
        truncatedText = truncatedText.slice(0, -1);
        textWidth = textCtx!.measureText(truncatedText).width;
      }
      setText(truncatedText + "...");
    } else {
      // 幅が最大幅以内の場合、元の文字列をそのまま返す
      setText(title);
    }
  }, [containerRef])

textTrim()関数をresizeのタイミングで発火させる

レスポンシブ対応でブラウザがリサイズしたタイミングで常に計算するようにします。

addEventListenerでresizeイベントを定義し、useEffectで初期描画時に定義しています。

  // リサイズ時に文字をトリムする
  useEffect(() => {
    textTrim();

    window.addEventListener("resize", textTrim);

    return () => {
      window.removeEventListener("resize", textTrim);
    }
  }, [])

コード全体

コード全体も載せておきます。

export default function TextTrimComponent() {
  const containerRef = useRef<HTMLDivElement>(null)
    const [text, setText] = useState("");
    // ユーザーインプットによって可変する文字列
    const title = "text_title";


  // テキストをコンテナサイズでトリミングする関数
  const textTrim = useCallback(() => {
    if (!containerRef.current) return;

    // テキストを収めるDOM横幅
    const maxTitleWidth = containerRef.current.getBoundingClientRect().width;

    // テキスト文字列の幅
    const textCtx = document.createElement("canvas").getContext("2d");
    textCtx!.font = '16px "arial,sans-serif"';
    let textWidth = textCtx!.measureText(title).width;

    // 幅が指定した最大幅を超えている場合、文字列を切り詰める
    if (textWidth > maxTitleWidth) {
      let truncatedText = title;
      while (textWidth > maxTitleWidth && truncatedText.length > 0) {
        truncatedText = truncatedText.slice(0, -1);
        textWidth = textCtx!.measureText(truncatedText).width;
      }
      setText(truncatedText + "...");
    } else {
      // 幅が最大幅以内の場合、元の文字列をそのまま返す
      setText(title);
    }
  }, [containerRef])

  // リサイズ時に文字をトリムする
  useEffect(() => {
    textTrim();

    window.addEventListener("resize", textTrim);

    return () => {
      window.removeEventListener("resize", textTrim);
    }
  }, [])

  return (
    <Container ref={containerRef}>
      <p>{text}</p>
    </Container>
  )
}

const Container = styled.div`
  background-color: #fff;
`

Summary

JavaScriptでトリミングする方法を書きましたが、できるならCSSのoverflow,white-spaceプロパティなどで簡単に実装したほうが軽いのでおすすめです。

p {
  width: 100px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

JavaScriptで実装するのは複雑かつ重くはなるので、CSSで実現できない時の対処として活用するのが良さそうです。

よかったらシェアしてね!
目次