1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React | Next.js】あまり面倒くさくない開閉アニメーション付きアコーディオンを作る

Last updated at Posted at 2023-08-23

※本記事は Zennに投稿した内容をそのまま複製しています

開閉アニメーションの付いたアコーディオンって実装面倒くさいですよね......。
っていうのは実は思い込みで、 max-heightCSS Variables を組み合わせれば超簡単に実装できます。

※動作確認はWindows各種ブラウザとAndroid Chromeのみです。試した方がいれば、コメ欄でSafariの挙動バグが無いか教えていただけると嬉しいです!

完成イメージ

CodeSandboxでサンプルを見る

※なぜか埋め込みできなかったのでリンクで失礼します。

※この記事のコードでは不要なスタイルを取り除いているので、この見た目通りにはならないかと思います。

必要なライブラリをインストールする

npm install clsx react-resize-detector
  • clsx: JSX内でclass名の分岐が簡単に記述するために使います。
  • react-resize-detector: リサイズ処理をお任せするために使います。

【本題】開閉アニメーション付きのアコーディオンを書く

Accordion.tsx
import { useState } from "react";
import clsx from "clsx";
import { useResizeDetector } from "react-resize-detector";

// アコーディオンのコンテンツの型
export interface ContentsProps {
  question: string;
  answer: string;
}

// 描画用のコンポーネント
const Accordion = () => {
  const contents: ContentsProps[] = [
    { question: "質問1", answer: "質問1の回答です。"},
    { question: "質問2", answer: "質問2の回答です。"}
  ];

  return (
    <div>
      <AccordionContainer contents={contents} />
    </div>
  );
};

// アコーディオンの項目を縦並びで整列しておく。
const AccordionContainer = ({ contents }: { contents: ContentsProps[] }) => {
  return (
    <div className="flex flex-col">
      {contents.map((theItem) => (
        <AccordionItem key={theItem.question} item={theItem} />
      ))}
    </div>
  );
};

const AccordionItem = ({ item }: { item: ContentsProps }) => {
  const [ isOpen, setIsOpen ] = useState(false); // 【状態】開閉を記憶
  const { height, ref } = useResizeDetector(); // react-resize-detector
  
    return (
    <dl className="overflow-hidden bg-white">
      <dt className="flex items-center cursor-pointer select-none" onClick={() => { setIsOpen(!isOpen); }}>
        <span>{item.question}</span>
      </dt>
      <dd
        style={{ "--content-height": height + "px" } as React.CSSProperties}
        className={`
	  overflow-hidden transition-[max-height] duration-500 ease-out-expo
	  ${clsx( isOpen === true ? "max-h-[var(--content-height)]" : "max-h-0")}
	`}
      >
        <div ref={ref}>
          <p className="border-t border-gray">{item.answer}</p>
        </div>
      </dd>
    </dl>
  );
}

export { Accordion }

たったこれだけで transition付きのアコーディオンが実現可能です。

※ ease-out-expo はtailwind.config.tsにて transitionTimingFunctionに "out-expo": "cubic-bezier(0.16, 1, 0.3, 1)", を追加しています。

実装のポイント

max-height の transition でアニメーションを行っています。
Tailwind CSSで言うと、max-h-0 から max-h-[var(--content-height)] に切り替わることでアニメーションが動くという仕組みです。

CSS変数への値の登録は style={{ "--content-height": height + "px" } as React.CSSProperties} で行っています。

コンテンツの高さは padding を含めたいので、空divに ref={ref} を付けて取得しています。

いかがでしたか。

いかがでしたか。
max-heightは神!!
いかがでしたか。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?