Reactで特定の要素の高さを取得するためのカスタムフックを作った : useContainerHeight

今回はreactで特定の要素の高さを取得したかったので、それをカスタムフック化してみました。

サンプルでは今回のカスタムフックで取得した高さの表示と普通にoffsetHeightで取得した場合を表示しています。

サンプルでは下記の操作が行えます

  • 高さを再取得する
  • 要素を非表示にする
  • 要素の高さを変更する

このカスタムフックはoffsetHeightで普通に高さを取得する方法と比べて以下の違いがあります。

  • cssで非表示(display: none;)にされてる高さが取得できる
  • リサイズによって要素の高さが変化してもその都度高さを取得
TypeScript
useContainerHeight.tsx
import { useState, useEffect, RefObject } from "react"; // 第2引数のオプションの型 type UseContainerHeightOptionProps = { // 任意の依存関係 dependencies?: any[]; // リサイズ時に高さの再計算を無効にする: trueで無効 disableResizeCalculation?: boolean; }; export const useContainerHeight = <T extends HTMLElement>( ref: RefObject<T>, { dependencies, disableResizeCalculation }: UseContainerHeightOptionProps = {} ): number => { // 要素の高さを保持するstate const [containerHeight, setContainerHeight] = useState(0); // 要素の高さを取得する const getHeight = () => { if (!ref.current) return; const targetContainer = ref.current; // 要素のコピーを作成し、親要素に追加する const ghostContainer = targetContainer.cloneNode(true) as T; targetContainer.parentNode?.appendChild(ghostContainer); // コピーのスタイルを設定して、高さを取得する ghostContainer.style.cssText = "display:block; height:auto; visibility:hidden;"; const height = ghostContainer.offsetHeight; // コピーを削除する targetContainer.parentNode?.removeChild(ghostContainer); // 要素の高さをstateに設定する setContainerHeight(height); }; // コンポーネントのマウント時とdisableResizeCalculation変更時にイベントリスナーを登録&削除する useEffect(() => { if (disableResizeCalculation) return; // リサイズ時に要素の高さを再計算する window.addEventListener("resize", getHeight); return () => { window.removeEventListener("resize", getHeight); }; }, [ref, disableResizeCalculation]); // コンポーネントのマウント時と任意の依存関係が変化したときにも要素の高さを計算する useEffect( () => { getHeight(); }, dependencies ? dependencies : [] ); return containerHeight; };

まずはサンプルコードの全体

以下のコードが基本的な使い方になります

TypeScript
index.tsx
import { useRef } from "react"; import { useContainerHeight } from "path/to/useContainerHeight"; const Home = () => { // 高さを取得したい要素 const ref = useRef<HTMLDivElement>(null); // カスタムフックで高さを取得 const containerHeight = useContainerHeight(ref); return ( <div> <div ref={ref}> 高さを取得したい要素 </div> <p>高さ:{containerHeight}</p> </div> ); }; export default Home;

大したコード量ではないですが、次でこのコードを分解して見てみます!

インポート

TypeScript
tsx
import { useRef } from "react"; import { useContainerHeight } from "path/to/useContainerHeight";

先程のuseContainerHeightをインポート
そしてrefを扱うのでuseRefもインポートしておきます。

高さを取得したい要素のrefを取得

useRefを使って高さを取得したい要素のrefを取得します

TypeScript
const Home = () => { // 高さを取得したい要素 const ref = useRef<HTMLDivElement>(null); return ( <div> <div ref={ref}> 高さを取得したい要素 </div> </div> ); }; export default Home;

先程のrefをuseContainerHeightで使い高さを取得

refをuseContainerHeightの第1引数にセットするだけ

その戻り値に高さが設定されているので、以下はcontainerHeightに高さが入ります。

TypeScript
const containerHeight = useContainerHeight(ref);

以上で使い方の説明は終わりです。

本当にただuseContainerHeightの第1引数にrefをセットするだけなので簡単です!

このカスタムフックの第2引数はちょっとしたオプションを用意しています。

プロパティ詳細デフォルト
dependencies任意の依存関係を設定できる。その設定した値が更新されたときに高さを再取得する。useEffectの第2引数と同じ[]any[]
disableResizeCalculationリサイズによる再計算を止めるfalseboolean

以下のように第2引数にオブジェクトで使いたいプロパティを設定します。

TypeScript
const containerHeight = useContainerHeight<HTMLAnchorElement>(ref, { dependencies: [hoge, fuga], //任意の依存関係 disableResizeCalculation: true //リサイズに再計算しない });

今回は高さを取得するためのカスタムフックuseContainerHeightを紹介してみました。

紹介したはいいですが、実際にどういうときに使うかって話ですが、僕はアコーディオンを実装するときに使っています。

アコーディオンは開閉アニメーションを付ける場合に高さがわからないとcssのtransitionが効かないからです。

そのときに重要なのが非表示の要素の高さを取得することです。アコーディオンは開く前に開いたときの高さを取得する必要があります。
そこで今回のuseContainerHeightを使うことで閉じている状態でも高さを取得することができるためcssのtransitionでアニメーションを実装することができます。

という経緯で出来上がったのが今回カスタムフックになります。以上です!