#8 ReactとNext.jsで動的な目次を実装する方法
- react
- Next.js
- typescript
- tailwindcss
目次の作成方法:Reactで動的な目次を実装しよう
こんにちは!今回は,Reactを使って動的な目次を作成する方法をご紹介します.目次は読者がコンテンツを把握しやすくするための重要な要素です.それでは,実装方法を見ていきましょう.
はじめに
動的な目次を作成することで,以下のような利点があります:
- ユーザーが記事の構造を素早く把握できる
- 長い記事でも簡単に目的の箇所にジャンプできる
- 現在読んでいる箇所がハイライトされる
最終的なコードのこのようになっています
TableOfContents.tsxのコード全体
use client;
import React, { useEffect, useState } from 'react';
import { Link as ScrollLink, animateScroll as scroll } from 'react-scroll';
import { motion } from 'framer-motion';
import { ChevronRight } from 'lucide-react';
interface TOCItem {
id: string;
text: string;
level: number;
}
const TableOfContents: React.FC = () => {
const [toc, setToc] = useState<TOCItem[]>([]);
const [activeId, setActiveId] = useState<string>('');
useEffect(() => {
const article = document.querySelector('article');
if (article) {
const headings = article.querySelectorAll('h1, h2, h3, h4');
const tocItems: TOCItem[] = Array.from(headings).map((heading) => ({
id: heading.id,
text: heading.textContent || '',
level: parseInt(heading.tagName.charAt(1)),
}));
setToc(tocItems);
}
const handleScroll = () => {
const headings = document.querySelectorAll('h1, h2, h3, h4');
const scrollPosition = window.scrollY;
for (let i = headings.length - 1; i >= 0; i--) {
const heading = headings[i] as HTMLElement;
if (heading.offsetTop <= scrollPosition + 100) {
setActiveId(heading.id);
break;
}
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<motion.nav
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className=table-of-contents bg-white p-6 rounded-lg shadow-md mt-10
>
<h2 className=text-2xl font-bold mb-4 text-gray-800>目次</h2>
<ul className=space-y-2>
{toc.map((item) => (
<motion.li
key={item.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
style={{ marginLeft: `${(item.level - 2) * 16}px` }}
>
<ScrollLink
to={item.id}
smooth={true}
duration={500}
offset={-70}
href={`#${item.id}`}
className={`flex items-center text-gray-700 hover:text-blue-500 transition-colors duration-200 ${
activeId === item.id ? 'text-blue-500 font-semibold' : ''
}`}
>
<ChevronRight
className={`mr-2 transition-transform duration-200 ${
activeId === item.id ? 'text-blue-500 transform rotate-90' : ''
}`}
size={16}
/>
{item.text}
</ScrollLink>
</motion.li>
))}
</ul>
</motion.nav>
);
};
export default TableOfContents;
それでは,実装の詳細について解説していきます.
必要なライブラリ
まず,以下のライブラリをインストールします:
npm install react-scroll framer-motion lucide-react
react-scroll
: スムーズなスクロール機能を提供framer-motion
: アニメーション効果を追加lucide-react
: アイコンを使用
コンポーネントの作成
次に,TableOfContents
コンポーネントを作成します.
TableOfContents.tsx
use client;
import React, { useEffect, useState } from 'react';
import { Link as ScrollLink, animateScroll as scroll } from 'react-scroll';
import { motion } from 'framer-motion';
import { ChevronRight } from 'lucide-react';
interface TOCItem {
id: string;
text: string;
level: number;
}
const TableOfContents: React.FC = () => {
const [toc, setToc] = useState<TOCItem[]>([]);
const [activeId, setActiveId] = useState<string>('');
// 以下,useEffectの実装など
// ...
return (
// JSXの実装
// ...
);
};
export default TableOfContents;
このコンポーネントは以下の主要な機能を持っています:
- ページ内のヘッダー要素(h1, h2, h3など)を検出し、目次項目として保存
- 現在表示されているセクションを追跡し、対応する目次項目をハイライト
- 目次項目をクリックすると、対応するセクションにスムーズにスクロール
では、各部分について詳しく見ていきましょう。
使用しているライブラリ
このコンポーネントでは、以下のライブラリを使用しています:
react-scroll
: ページ内のスムーズなスクロールを実現するためのライブラリframer-motion
: アニメーション効果を追加するためのライブラリlucide-react
: アイコンを表示するためのライブラリ
これらのライブラリを使用することで、より洗練された UI とインタラクションを実現しています。
型定義
目次の各項目の型を TOCItem
インターフェースで定義しています:
interface TOCItem {
id: string; // ヘッダー要素のid
text: string; // ヘッダーのテキスト内容
level: number; // ヘッダーのレベル(h1 = 1, h2 = 2, ...)
}
状態管理
コンポーネント内で2つの状態を管理しています:
const [toc, setToc] = useState<TOCItem[]>([]);
const [activeId, setActiveId] = useState<string>('');
toc
: 検出されたヘッダー要素から生成された目次項目の配列activeId
: 現在表示されているセクションのID
目次アイテムの取得
まず最初に,記事内の見出しを取得し,目次アイテムを生成する方法を見ていきましょう.この処理にはuseEffect
フックを使用します.
import { useEffect, useState } from 'react';
interface TOCItem {
id: string;
text: string;
level: number;
}
const TableOfContents = () => {
const [toc, setToc] = useState<TOCItem[]>([]);
useEffect(() => {
const article = document.querySelector('article');
if (article) {
const headings = article.querySelectorAll('h1, h2, h3, h4');
const tocItems: TOCItem[] = Array.from(headings).map((heading) => ({
id: heading.id,
text: heading.textContent || '',
level: parseInt(heading.tagName.charAt(1)),
}));
setToc(tocItems);
}
}, []);
// ...
}
このコードでは以下のことを行っています:
article
要素を取得します.これは記事の本文を含む要素です.querySelectorAll
を使って,article
内のすべてのh1
,h2
,h3
,h4
要素を取得します.- 取得した見出し要素を
Array.from
でリストに変換し,map
関数で各見出しの情報を抽出します. - 各見出しについて,以下の情報を含むオブジェクトを作成します:
id
: 見出しのID(後でリンクに使用)text
: 見出しのテキスト内容level
: 見出しのレベル(h1なら1,h2なら2,など)
- 作成した目次アイテムのリストを
setToc
関数でステートに保存します.
この方法により,記事の構造が変更されても,目次が自動的に更新されるようになります.
スクロール位置の監視
スクロール位置を監視し,現在のセクションをハイライトします.
useEffect(() => {
// ...
const handleScroll = () => {
const headings = document.querySelectorAll('h1, h2, h3, h4');
const scrollPosition = window.scrollY;
for (let i = headings.length - 1; i >= 0; i--) {
const heading = headings[i] as HTMLElement;
if (heading.offsetTop <= scrollPosition + 100) {
setActiveId(heading.id);
break;
}
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
スタイリングとアニメーション
Tailwind CSSとFramer Motionを使用して,目次をスタイリングし,アニメーション効果を追加します.
return (
<motion.nav
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5 }}
className=table-of-contents bg-white p-6 rounded-lg shadow-md mt-10
>
<h2 className=text-2xl font-bold mb-4 text-gray-800>目次</h2>
<ul className=space-y-2>
{toc.map((item) => (
<motion.li
key={item.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
style={{ marginLeft: `${(item.level - 2) * 16}px` }}
>
<ScrollLink
to={item.id}
smooth={true}
duration={500}
offset={-70}
href={`#${item.id}`}
className={`flex items-center text-gray-700 hover:text-blue-500 transition-colors duration-200 ${
activeId === item.id ? 'text-blue-500 font-semibold' : ''
}`}
>
<ChevronRight
className={`mr-2 transition-transform duration-200 ${
activeId === item.id ? 'text-blue-500 transform rotate-90' : ''
}`}
size={16}
/>
{item.text}
</ScrollLink>
</motion.li>
))}
</ul>
</motion.nav>
);
まとめ
以上が,Reactを使用した動的な目次の作成方法です.この実装により,以下の機能を持つ目次が作成できます:
- 記事内の見出しを自動的に取得
- スムーズなスクロール機能
- 現在のセクションのハイライト
- アニメーション効果
目次を実装することで,読者の記事理解度とユーザーエクスペリエンスを向上させることができます.ぜひ,自分のブログやウェブサイトに取り入れてみてください!