目次
【React対応】ぬるっと動く!スムーススクロールを自作で完全制御する方法
「セクションリンクをクリックしたとき、ページを滑らかにスクロールさせたい」──そんな場面は、ブログやドキュメント系のページでよくありますよね。
本記事では、React(Next.js)環境で「完全に制御されたスムーススクロール」を実現する方法を、JavaScript(TypeScript)によるアニメーション制御をベースに紹介します。
✅ この記事で実現すること
- 任意のセクションIDへのスムーススクロール
- スクロール速度のカスタマイズ(ミリ秒指定)
- 固定ヘッダーに隠れないスクロール位置調整
- モバイル・PCの表示幅に応じた挙動の切り替え
✅ 実装コード:React用スムーススクロール
以下のコンポーネントを使えば、HTML内のセクションリンク(例: <a href="#section1">
)を滑らかにスクロールさせることができます。
'use client'
import { useEffect } from 'react'
import parse from 'html-react-parser'
export default function TOCScroll({ content }: { content: string }) {
useEffect(() => {
const timeout = setTimeout(() => {
const anchors = document.querySelectorAll('.toc_list a[href^="#"]')
const handleClick = (e: Event) => {
e.preventDefault()
const href = (e.currentTarget as HTMLAnchorElement).getAttribute('href')
if (!href) return
const target = document.querySelector(href)
if (target) {
const isMobile = window.innerWidth < 768
const offset = isMobile ? 60 : 180
const targetY = target.getBoundingClientRect().top + window.scrollY - offset
smoothScrollTo(targetY, 1000)
}
}
anchors.forEach(anchor => anchor.addEventListener('click', handleClick))
return () => {
anchors.forEach(anchor => anchor.removeEventListener('click', handleClick))
}
}, 0)
return () => clearTimeout(timeout)
}, [content])
return <>{parse(content)}</>
}
function smoothScrollTo(targetY: number, duration = 1000) {
const startY = window.scrollY
const diff = targetY - startY
const startTime = performance.now()
const easeInOutCubic = (t: number) =>
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
const step = (currentTime: number) => {
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
const eased = easeInOutCubic(progress)
window.scrollTo(0, startY + diff * eased)
if (progress < 1) requestAnimationFrame(step)
}
requestAnimationFrame(step)
}
✅ どうして scrollIntoView を使わなかったの?
よくある方法として element.scrollIntoView({ behavior: 'smooth' })
もありますが、以下のような課題がありました:
- 固定ヘッダーがあると対象要素が隠れる
- スクロールの速さを調整できない
- iOS Safari や一部環境で効かないことがある
そのため今回は、自作アニメーション(requestAnimationFrame)+イージング制御によって、より細かく・正確にコントロールしています。
✅ 一般的なスムーススクロールの手法比較
方法 | 制御性 | 安定性 | 特徴 |
---|---|---|---|
CSS scroll-behavior | × | △ | 簡単・全体適用に便利 |
scrollIntoView | △ | △ | 手軽だけど環境依存あり |
scrollTo + offset | ◎ | ◎ | 固定ヘッダー対策も可能 |
requestAnimationFrame | ◎ | ◎ | 最高の柔軟性と自然な動き |
✅ 補足:対象のHTMLが文字列な場合は?
この例では html-react-parser
を使用して、HTML文字列を安全に描画しています。以下のように使用できます:
import parse from 'html-react-parser'
const html = '<h2 id="section1">見出し</h2>'
return <>{parse(html)}</>
✅ まとめ
スムーススクロールを自前で制御できるようになると、どんな構成のページでも自由自在にUXをデザインできるようになります。
特に、固定ヘッダーのあるページや、セクションジャンプを多用するドキュメント系のサイトでは、今回のような実装が大きな価値を発揮します。
📌 次のステップとしておすすめ:
- 共通フック化して複数ページで再利用
- スクロール完了後にアニメーションを追加
- URLのハッシュを動的に反映(ナビゲーション強化)
ぜひあなたのプロジェクトにもこの「本気のスムーススクロール」を導入して、気持ちよく操作できるUIを作ってみてください!