なるべく汎用的なツールチップを作る


以前、JavaScript(React)でアコーディオンのコンポーネントを自作して面倒だという記事を書いたのですが、今回はツールチップの自作実装の記録です。

こちらも何かライブラリ使えば良いじゃないで終わりそうな話ですが、作らざるを得ない時もあります。

実装したツールチップのタイプ

  • 要素にカーソルが当たった時に(ホバー)表示する。
  • 一度に表示するのは一つだけ。
  • 上下左右のどこに表示するか設定できる。

汎用的に使えるツールチップ(コンポーネント)にするために考えたポイント

  • コンポーネント単体で配置できるようにしたい。
  • positionと座標計算。
  • z-index 問題。

コンポーネント単体で配置できるようにしたい

自作の時もライブラリ使うときにもありがちなのが、

ツールチップ表示対象の要素をツールチップコンポーネントでラップする仕様です。コンポーネント的に書くと以下のような感じ。messageの値がツールチップの内容として表示される形です。

<Tooltip
	message="ホッキョクグマのこと"
>
	<p>
		しろくま
	</p>
</Tooltip>

(ツールチップが親要素になるわけではないので)直感的でない+コンテンツ量が多いと可読性が悪くなっていくことが気になっていました。

という事で、今回はツールチップを表示したい要素の直後にコンポーネントを配置する形にしたい。以下のような感じです。

<p>
	しろくま
</p>
<Tooltip
	message="ホッキョクグマのこと"
/>

コンポーネント内は、ラッパー要素とツールチップの要素で構成し、ラッパー要素に座標指定を行います。

// styleで座標を指定する
<div style={style}>
	<div style={{position: absolute}}>
		{children}
  </div>
</div>

positionと座標計算

今回はツールチップ単独のコンポーネントを作るので、親要素にrelativeを確約できません。そのため position は fixed 一択になりました。

fixedだと以下の問題があります。

  • ホバー時にスクロールが行われた時にツールチップが追随しない。
  • 祖先要素でfilterが使われていると基準位置がその要素になる。

二点を考慮した表示位置の座標計算を行います。

z-ndex 問題

要素のネスト状態によっては他の要素がツールチップの前面に表示されてしまう可能性があります。z-index: 999を指定することである程度解決できます。

常に最前面に表示したい場合は、ツールチップの要素(DOM)を移動する等の処理を追加します。

実装内容をまとめると

  • 配置したコンポーネントの直前の要素の位置(座標)を取得。
    • 取得した座標からツールチップを表示する座標を計算。
    • ツールチップの要素に表示位置を指定する。
  • 直前の兄弟要素がホバーされた時に
    • (ツールチップの要素を最前面に移動。)
    • ツールチップの要素を表示状態にする。
  • 直前の兄弟要素がホバー状態ではなくなった時に
    • ツールチップの要素を非表示状態にする。

といった感じ。アコーディオンの時と比べると楽でした。

と、話したところで「ツールチップをたくさん出すような設計はそもそも良くない。使わないで済むなら使わない方が良い。」という結論をおいて終わりにしたいと思います。