どんなことをしたいかというと、
- 文字列が長くて表示しきれないときは省略表記する
- マウスホバーで文字列全体をツールチップ表示する
というコンポーネントを作成してみます。
動作は Chrome バージョン 92.0.4515.107 で確認しています。
この表示をしたコンポーネントのコードは以下の通りです。
export const Simple = () => <span
style={{
display: 'block',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
title="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
>abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz</span>
- style に、文字列が表示に収まらなかったら省略表記するように指定
- title に、ツールチップで表示する文字列を設定
ということをしています。
ただ、このままだと省略表記が不要な文字長でもツールチップが表示されます。省略表記していないときはツールチップがでないようにしたいと思います。
作成したコード
.text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
import React, { useRef, useState, useEffect } from 'react';
import './text.css';
type TextProps = Omit<JSX.IntrinsicElements['span'], 'children'>
& { children: string };
export const Text = (props: TextProps) => {
const refOuter = useRef<HTMLSpanElement>(null);
const refInner = useRef<HTMLSpanElement>(null);
// 省略表記のとき true
const [isEllipsis, setIsEllipsis] = useState<boolean>(false);
// リサイズ監視
useEffect(() =>{
const resizeObserver = new ResizeObserver(() =>{
const rectOuter = refOuter.current?.getBoundingClientRect();
const rectInner = refInner.current?.getBoundingClientRect();
// 外枠 < 内枠 なら省略表記
setIsEllipsis((rectOuter?.width ?? 0) < (rectInner?.width ?? 0));
});
const el = refOuter.current as Element;
resizeObserver.observe(el);
return () => resizeObserver.unobserve(el);
});
const {
children,
...etc
} = props;
// 省略表記になるとき title を設定
const title = isEllipsis && { title: children };
return (
<span
ref={refOuter}
className="text"
{...etc}
>
<span
ref={refInner}
{...title}
>{children}</span>
</span>
);
};
という Text コンポーネントにすると、
<Text>abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz<Text>
の様に使えます。
解説
- 省略表記している → title の props を設定
- 省略表記していない → title の props は無し
ということをする為に色々していきます。
<span
ref={refOuter}
className="text"
{...etc}
>
<span
ref={refInner}
{...title}
>{children}</span>
</span>
span を親子な状態にしています。
親
- display: block; でブロック要素にして親要素の幅に沿わせる。
- 文字列が収まらなかったときに省略表記するスタイルの設定。
- 要素のリサイズを監視し、親と子の幅を比較して、ツールチップが必要かどうかを isEllipsis に反映。
親は overflow: hidden; にしているので、
親の幅 < 子の幅 のときは省略表記されている、と判定します。
子
- 文字列の表示
- 文字列が
省略表記されたときはツールチップで全文表示
省略表記されていなければツールチップは無し
という役割をしています。
// リサイズ監視
useEffect(() =>{
const resizeObserver = new ResizeObserver(() =>{
const rectOuter = refOuter.current?.getBoundingClientRect();
const rectInner = refInner.current?.getBoundingClientRect();
// 外枠 < 内枠 なら省略表記
setIsEllipsis((rectOuter?.width ?? 0) < (rectInner?.width ?? 0));
});
const el = refOuter.current as Element;
resizeObserver.observe(el);
return () => resizeObserver.unobserve(el);
});
useEffect で親spanのリサイズ監視/監視解除をします。
リサイズの監視には ResizeObserver を使用します。
リサイズが発生したときは、親と子の span の幅を比較して、省略表記されているか判定した結果を isEllipsis に反映します。
ここまでするぐらいなら、もう常に title ありでも良いんじゃないか?という気も湧いてきますが、何かの参考になりましたら幸いです。
プロを目指す人のためのTypeScript入門 (Amazon)
コメント