0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactの `useRef` でノード要素を参照する活用例まとめ

Last updated at Posted at 2025-06-29

はじめに

ReactでDOM要素を直接操作したいとき、useRef フックを使うことで対象ノードにアクセスすることができます。本記事では、以下のような代表的なユースケースを実例コードとともに紹介します。

1. スクロール操作

ボタンで特定のセクションに移動

ScrollExample.tsx
import React, { useRef } from "react"

export default function ScrollExample() {
  const part1Ref = useRef<HTMLDivElement>(null)
  const part2Ref = useRef<HTMLDivElement>(null)
  const part3Ref = useRef<HTMLDivElement>(null)

  const handleScroll = (ref: React.RefObject<HTMLDivElement>) => {
    ref.current?.scrollIntoView({ behavior: "smooth" })
  }

  return (
    <>
      <div style={{ position: "fixed", background: "#fff", zIndex: 10 }}>
        <button onClick={() => handleScroll(part1Ref)}>第一部</button>
        <button onClick={() => handleScroll(part2Ref)}>第二部</button>
        <button onClick={() => handleScroll(part3Ref)}>第三部</button>
      </div>
      <div ref={whiteRef} style={{ height: "100vh", background: "white" }} />
      <div ref={yellowRef} style={{ height: "100vh", background: "yellow" }} />
      <div ref={blackRef} style={{ height: "100vh", background: "black" }} />
    </>
  )
}

🔍 解説:

  • ref を使うことで、各セクション(第一部/第二部/第三部)のDOMノードに直接アクセスできます。
  • scrollIntoView() メソッドは、その要素が画面に見えるようにスクロールしてくれる便利なAPIです。class名やidに依存しない ので、DOM構造が変わっても安心してスクロールできます。

2. 初期フォーカスの設定

ページを開いたら自動で入力欄にカーソルを合わせる

AutofocusInput.tsx
import React, { useEffect, useRef } from "react"

export default function AutofocusInput() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return (
    <div>
      <label>Email:</label>
      <input ref={inputRef} type="email" placeholder="your@email.com" />
    </div>
  )
}

🔍 解説:

  • フォームで「ページを開いたら自動的に入力欄にフォーカスさせたい」ことはよくあります。
  • useRef でinput要素を取得し、useEffectで初回マウント時に .focus() を呼べば、実現可能です。React的にフォーカスを操作したいときはこのパターンが定番です。

3. Videoプレイヤーの再生・停止制御

VideoPlayer.tsx
import React, { useRef, useState } from "react"

export default function VideoPlayer() {
  const videoRef = useRef<HTMLVideoElement>(null)
  const [isPlaying, setIsPlaying] = useState(false)

  const togglePlay = () => {
    if (!videoRef.current) return
    isPlaying ? videoRef.current.pause() : videoRef.current.play()
    setIsPlaying((prev) => !prev)
  }

  return (
    <div>
      <video ref={videoRef} width={320}>
        <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4" />
      </video>
      <button onClick={togglePlay}>{isPlaying ? "Pause" : "Play"}</button>
    </div>
  )
}

🔍 解説:

  • ref を使って <video> 要素の .play().pause() を直接呼び出すことで、状態に応じた制御が可能になります。HTML5のビデオ要素はコントロールが複雑になりやすいので、useRef 経由で直接操作できると便利です。

4. モーダル外クリックで閉じる

ClickOutsideModal.tsx
import React, { useEffect, useRef } from "react"

interface Props {
  onClose: () => void
}

export default function ClickOutsideModal({ onClose }: Props) {
  const modalRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const handleClick = (e: MouseEvent) => {
      if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
        onClose()
      }
    }

    document.addEventListener("mousedown", handleClick)
    return () => document.removeEventListener("mousedown", handleClick)
  }, [onClose])

  return (
    <div ref={modalRef} style={{ background: "#fff", padding: "2rem" }}>
      <h2>モーダル</h2>
      <p>外側をクリックすると閉じます</p>
    </div>
  )
}

🔍 解説:

  • ref を使うことで「自分自身がクリックされたかどうか」を判定できます。
  • Reactでは document.addEventListener を使って外側のクリックを検知し、ref.current.contains() でモーダル外かを判断します。モーダルやドロップダウンの定番パターンです。

5. フィールドノート(メモチャット)

このコンポーネントは、簡易的なチャット風のメモアプリです。

FieldNotes.tsx
import * as React from "react"

export default function FieldNotes() {
  const [notes, setNotes] = React.useState([
    "ReactコンポーネントはUIの見た目とロジックをまとめて管理します。",
    "関数を組み合わせるように、コンポーネントも組み合わせてUIを作成できます。",
    "JSXはJavaScriptの力とHTMLの読みやすさを融合しています。",
    "フックを使うことで、ロジックの再利用が簡単になります。",
  ])

  const newNoteRef = React.useRef<HTMLLIElement>(null)

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const form = e.target as HTMLFormElement
    const formData = new FormData(form)
    const newNote = formData.get("note")

    if (typeof newNote === "string" && newNote.trim()) {
      setNotes([...notes, newNote])
      form.reset()
    }
  }

  React.useEffect(() => {
    if (newNoteRef.current) {
      newNoteRef.current.scrollIntoView({ behavior: "smooth" })
    }
  })

  return (
    <div>
      <article>
        <h1>Field Notes</h1>
        <div>
          <ul>
            {notes.map((msg, index) => (
              <li
                ref={index === notes.length - 1 ? newNoteRef : null}
                key={index}
              >
                {msg}
              </li>
            ))}
          </ul>
          <form onSubmit={handleSubmit}>
            <input
              required
              type="text"
              name="note"
              placeholder="メモを入力してください..."
            />
            <button type="submit">
              送信
            </button>
          </form>
        </div>
      </article>
    </div>
  )
}

🔍 解説:

  • useState でメモのリストを管理し、useRef で最新メモのDOM要素を参照します。
  • メモを追加すると、scrollIntoView({ behavior: \"smooth\" }) で自動的に最新メモへスクロールします。
  • フォーム送信時に新しいメモを追加し、空欄や空白のみの入力は無視します。

Certainly! Here’s an article section for your FollowTheLeader component, stripped of styling and following your requested format:

6. クリックした位置にマーブルが移動する

クリックした位置にマーブル(円)がスムーズに移動します。

FollowTheLeader.tsx
import React, { useEffect, useRef, useState } from "react"

export default function FollowTheLeader() {
  const getCenter = (): [number, number] =>
    [window.innerWidth / 2 - 32, window.innerHeight / 2 - 32]
  const [position, setPosition] = useState<[number, number]>([0, 0])
  const boxRef = useRef<HTMLDivElement>(null)
  const [initialized, setInitialized] = useState(false)

  useEffect(() => {
    setPosition(getCenter())
    setInitialized(true)
    const handleResize = () => setPosition(getCenter())
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [])

  const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
    if (!boxRef.current) return
    const { width, height } = boxRef.current.getBoundingClientRect()
    setPosition([
      event.clientX - width / 2,
      event.clientY - height / 2,
    ])
  }

  return (
    <div onClick={handleClick}>
      <div
        ref={boxRef}
        style={{
          position: "absolute",
          left: 0,
          top: 0,
          width: 64,
          height: 64,
          borderRadius: "50%",
          transform: `translate(${position[0]}px, ${position[1]}px)`,
          opacity: initialized ? 1 : 0,
        }}
      />
    </div>
  )
}

🔍 解説:

  • useRef でマーブルのDOM要素を参照し、クリック時の座標からマーブルの中心がクリック位置に来るように座標を計算しています。
  • ウィンドウリサイズ時も中央に再配置されるように useEffect でイベントリスナーを追加しています。

終わりに

useRef を使えば、ReactでもDOM操作が必要な場面に柔軟に対応できます。特に次のような場面で効果を発揮します:

  • 初期化時の操作(フォーカス、スクロール)
  • メディア制御
  • モーダルやドロップダウンなどのUI操作
  • Intersection Observerを使った表示判定
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?