Webサイトでボタンをクリックしたり、画像の上にマウスを乗せると色が変わったりする動作、不思議に思ったことはありませんか?
この「ユーザーの操作に反応する仕組み」を作るのがマウスイベントです。Reactでは、簡単にマウスイベントを扱えるんですよ。
今回は、Reactでのマウスイベントの使い方を、初心者の方でも分かりやすく解説していきます。クリック、ホバー、ドラッグなど、様々な操作を実装できるようになりましょう!
マウスイベントとは?基本を理解しよう

マウスイベントは、ユーザーがマウスで行う操作を検知する仕組みです。
イベントの種類
マウスには、色々な操作がありますよね。
主な操作:
- クリック(押して離す)
- ダブルクリック(2回連続でクリック)
- ホバー(マウスを乗せる)
- ドラッグ(押しながら移動)
- 右クリック
それぞれに対応するイベントが用意されています。
Reactでの基本的な書き方
HTMLとの違い:
通常のHTMLでは、こう書きます。
<button onclick="handleClick()">クリック</button>
Reactでは:
<button onClick={handleClick}>クリック</button>
微妙な違いですが、大切なポイントがあります。
onclick→onClick(キャメルケース)"handleClick()"→{handleClick}(関数を直接渡す)
主要なマウスイベント一覧
Reactで使える主なマウスイベントを見ていきましょう。
クリック系イベント
onClick
最も基本的なイベントです。マウスをクリックしたときに発火します。
<button onClick={() => console.log('クリックされた')}>
クリックしてね
</button>
onDoubleClick
ダブルクリックされたときに発火します。
<div onDoubleClick={() => console.log('ダブルクリック')}>
ダブルクリックしてください
</div>
onContextMenu
右クリック(コンテキストメニュー)で発火します。
<div onContextMenu={(e) => {
e.preventDefault();
console.log('右クリックされた');
}}>
右クリックしてみて
</div>
マウス移動系イベント
onMouseEnter
マウスが要素の上に入ってきたときに発火しますよ。
<div onMouseEnter={() => console.log('マウスが入った')}>
ホバーエリア
</div>
onMouseLeave
マウスが要素から出ていったときに発火します。
<div onMouseLeave={() => console.log('マウスが出た')}>
ホバーエリア
</div>
onMouseOver / onMouseOut
onMouseEnter/onMouseLeaveと似ていますが、子要素でも発火するという違いがあります。
onMouseMove
マウスが要素上で動いたときに発火します。頻繁に呼ばれるので注意が必要ですね。
<div onMouseMove={(e) => console.log(`位置: ${e.clientX}, ${e.clientY}`)}>
マウスを動かしてみて
</div>
マウスボタン系イベント
onMouseDown
マウスボタンを押した瞬間に発火します。
<button onMouseDown={() => console.log('押した')}>
押してみて
</button>
onMouseUp
マウスボタンを離した瞬間に発火します。
<button onMouseUp={() => console.log('離した')}>
離してみて
</button>
実例:シンプルなクリックカウンター
実際に動くコードで理解を深めましょう。
基本的なクリックカウンター
import { useState } from 'react';
function ClickCounter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>クリック回数: {count}</p>
<button onClick={handleClick}>
クリックしてね
</button>
</div>
);
}
ポイント:
useStateで状態を管理handleClick関数でカウントを増やすonClickでイベントハンドラを設定
説明
useState:
Reactで状態(変化する値)を管理するための機能です。countが現在の値、setCountが値を変更する関数ですね。
イベントハンドラ:
handleClickのような、イベントが発生したときに実行される関数のことです。
イベントオブジェクトの活用
イベントハンドラには、自動的にイベントオブジェクトが渡されます。
イベントオブジェクトとは
マウスイベントの詳細情報を持つオブジェクトです。
function handleClick(event) {
console.log('クリックされた位置:', event.clientX, event.clientY);
console.log('押されたボタン:', event.button);
}
<button onClick={handleClick}>クリック</button>
主要なプロパティ
マウス座標:
| プロパティ | 説明 |
|---|---|
clientX | ビューポート(表示領域)内のX座標 |
clientY | ビューポート内のY座標 |
pageX | ページ全体でのX座標 |
pageY | ページ全体でのY座標 |
screenX | スクリーン全体でのX座標 |
screenY | スクリーン全体でのY座標 |
ボタン情報:
| プロパティ | 説明 |
|---|---|
button | 押されたボタン(0=左, 1=中, 2=右) |
buttons | 現在押されているボタンの組み合わせ |
修飾キー:
| プロパティ | 説明 |
|---|---|
ctrlKey | Ctrlキーが押されているか |
shiftKey | Shiftキーが押されているか |
altKey | Altキーが押されているか |
metaKey | Cmd(Mac)やWin(Windows)キーが押されているか |
実例:クリック位置を表示
import { useState } from 'react';
function ClickPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleClick = (e) => {
setPosition({
x: e.clientX,
y: e.clientY
});
};
return (
<div
onClick={handleClick}
style={{
height: '300px',
border: '1px solid black',
cursor: 'crosshair'
}}
>
<p>クリックした位置: ({position.x}, {position.y})</p>
<p>どこでもクリックしてみてください</p>
</div>
);
}
ホバーエフェクトの実装

マウスを乗せると色が変わる、よく見る動作を作ってみましょう。
基本的なホバーエフェクト
import { useState } from 'react';
function HoverButton() {
const [isHovered, setIsHovered] = useState(false);
return (
<button
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
backgroundColor: isHovered ? '#4CAF50' : '#ddd',
color: isHovered ? 'white' : 'black',
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
transition: 'all 0.3s'
}}
>
ホバーしてみて
</button>
);
}
仕組み:
isHoveredという状態を用意- マウスが入ったら
trueに設定 - マウスが出たら
falseに設定 - 状態に応じてスタイルを変更
複数の要素のホバー管理
import { useState } from 'react';
function HoverList() {
const [hoveredId, setHoveredId] = useState(null);
const items = [
{ id: 1, name: 'アイテム1' },
{ id: 2, name: 'アイテム2' },
{ id: 3, name: 'アイテム3' }
];
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map(item => (
<li
key={item.id}
onMouseEnter={() => setHoveredId(item.id)}
onMouseLeave={() => setHoveredId(null)}
style={{
padding: '10px',
backgroundColor: hoveredId === item.id ? '#e3f2fd' : 'white',
border: '1px solid #ccc',
marginBottom: '5px',
cursor: 'pointer',
transition: 'background-color 0.2s'
}}
>
{item.name}
</li>
))}
</ul>
);
}
ドラッグ&ドロップの実装
要素をドラッグできるようにしてみましょう。
シンプルなドラッグ可能な要素
import { useState } from 'react';
function DraggableBox() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
const handleMouseDown = (e) => {
setIsDragging(true);
setDragStart({
x: e.clientX - position.x,
y: e.clientY - position.y
});
};
const handleMouseMove = (e) => {
if (!isDragging) return;
setPosition({
x: e.clientX - dragStart.x,
y: e.clientY - dragStart.y
});
};
const handleMouseUp = () => {
setIsDragging(false);
};
return (
<div
style={{ height: '400px', position: 'relative', border: '1px solid #ccc' }}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
<div
onMouseDown={handleMouseDown}
style={{
position: 'absolute',
left: `${position.x}px`,
top: `${position.y}px`,
width: '100px',
height: '100px',
backgroundColor: isDragging ? '#4CAF50' : '#2196F3',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: isDragging ? 'grabbing' : 'grab',
userSelect: 'none'
}}
>
ドラッグしてね
</div>
</div>
);
}
仕組み:
onMouseDownでドラッグ開始onMouseMoveで位置を更新onMouseUpでドラッグ終了
合成イベント(SyntheticEvent)の理解
Reactでは、ブラウザのネイティブイベントを合成イベントでラップしています。
合成イベントとは
クロスブラウザ対応を自動化:
ブラウザによってイベントの挙動が異なることがありますが、Reactの合成イベントはすべてのブラウザで同じように動作します。
メモリ効率が良い:
イベントオブジェクトは再利用されるため、メモリ効率が良いんですよ。
ネイティブイベントへのアクセス
もし本当のネイティブイベントが必要な場合は、nativeEventプロパティを使います。
function handleClick(event) {
console.log('合成イベント:', event);
console.log('ネイティブイベント:', event.nativeEvent);
}
イベントの伝播制御
イベントは、子要素から親要素へと伝播していきます。
イベントバブリング
function Parent() {
return (
<div onClick={() => console.log('親がクリックされた')}>
親要素
<button onClick={() => console.log('子がクリックされた')}>
子要素
</button>
</div>
);
}
ボタンをクリックすると、両方のログが出力されます。
出力順:
- 子がクリックされた
- 親がクリックされた
伝播を止める
stopPropagation:
親要素への伝播を止めたい場合に使います。
function Parent() {
return (
<div onClick={() => console.log('親がクリックされた')}>
親要素
<button onClick={(e) => {
e.stopPropagation();
console.log('子がクリックされた');
}}>
子要素(伝播しない)
</button>
</div>
);
}
これで、ボタンをクリックしても親のイベントは発火しません。
デフォルト動作を防ぐ
preventDefault:
リンクのクリックやフォーム送信などのデフォルト動作を防ぎます。
function CustomLink() {
const handleClick = (e) => {
e.preventDefault();
console.log('リンクがクリックされたけど、遷移しない');
};
return (
<a href="https://example.com" onClick={handleClick}>
クリックしても遷移しないリンク
</a>
);
}
パフォーマンス最適化
マウスイベントは頻繁に発火するため、パフォーマンスに注意が必要です。
useCallbackでメモ化
イベントハンドラをメモ化することで、不要な再レンダリングを防げます。
import { useState, useCallback } from 'react';
function OptimizedComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 依存配列が空なので、関数は再生成されない
return (
<button onClick={handleClick}>
クリック回数: {count}
</button>
);
}
スロットリング(間引き)
onMouseMoveのような頻繁に発火するイベントでは、スロットリングが有効です。
import { useState, useRef } from 'react';
function ThrottledMouseMove() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const throttleRef = useRef(false);
const handleMouseMove = (e) => {
if (throttleRef.current) return;
throttleRef.current = true;
setTimeout(() => {
throttleRef.current = false;
}, 100); // 100ms間隔で実行
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div
onMouseMove={handleMouseMove}
style={{ height: '300px', border: '1px solid black' }}
>
<p>マウス位置: ({position.x}, {position.y})</p>
</div>
);
}
デバウンス(遅延実行)
入力が止まってから処理を実行したい場合は、デバウンスを使います。
import { useState, useEffect } from 'react';
function DebouncedHover() {
const [isHovered, setIsHovered] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
useEffect(() => {
let timer;
if (isHovered) {
timer = setTimeout(() => {
setShowTooltip(true);
}, 500); // 500ms後に表示
} else {
setShowTooltip(false);
}
return () => clearTimeout(timer);
}, [isHovered]);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ position: 'relative', padding: '10px' }}
>
ホバーしてください
{showTooltip && (
<div style={{
position: 'absolute',
background: 'black',
color: 'white',
padding: '5px',
borderRadius: '3px',
top: '100%',
left: '0'
}}>
ツールチップが表示されました
</div>
)}
</div>
);
}
TypeScriptでの型定義
TypeScriptを使う場合、イベントの型を正しく指定しましょう。
基本的な型指定
import { MouseEvent } from 'react';
function TypedButton() {
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
console.log('ボタンがクリックされた');
};
return <button onClick={handleClick}>クリック</button>;
}
主要な型
MouseEvent:
MouseEvent<HTMLElement>
MouseEvent<HTMLButtonElement>
MouseEvent<HTMLDivElement>
MouseEvent<HTMLAnchorElement>
要素の種類に応じて、型パラメータを変えます。
イベントハンドラの型
type ClickHandler = (event: MouseEvent<HTMLButtonElement>) => void;
function Component() {
const handleClick: ClickHandler = (event) => {
console.log('型安全なクリックハンドラ');
};
return <button onClick={handleClick}>クリック</button>;
}
カスタムフックでの型定義
import { useState, MouseEvent } from 'react';
function useHover() {
const [isHovered, setIsHovered] = useState(false);
const handlers = {
onMouseEnter: (e: MouseEvent<HTMLElement>) => setIsHovered(true),
onMouseLeave: (e: MouseEvent<HTMLElement>) => setIsHovered(false)
};
return [isHovered, handlers] as const;
}
function HoverComponent() {
const [isHovered, hoverHandlers] = useHover();
return (
<div {...hoverHandlers}>
{isHovered ? 'ホバー中' : 'ホバーしてね'}
</div>
);
}
実用的なパターン集

よく使われるパターンをまとめました。
パターン1:長押し検出
import { useState, useRef } from 'react';
function LongPressButton() {
const [isLongPress, setIsLongPress] = useState(false);
const timerRef = useRef(null);
const handleMouseDown = () => {
timerRef.current = setTimeout(() => {
setIsLongPress(true);
console.log('長押しされた');
}, 1000); // 1秒間押し続けたら発火
};
const handleMouseUp = () => {
clearTimeout(timerRef.current);
setIsLongPress(false);
};
return (
<button
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
style={{
padding: '20px',
backgroundColor: isLongPress ? '#4CAF50' : '#2196F3',
color: 'white',
border: 'none',
borderRadius: '5px'
}}
>
1秒間押し続けてね
</button>
);
}
パターン2:カスタムコンテキストメニュー
import { useState } from 'react';
function CustomContextMenu() {
const [menuPosition, setMenuPosition] = useState(null);
const handleContextMenu = (e) => {
e.preventDefault();
setMenuPosition({ x: e.clientX, y: e.clientY });
};
const handleClick = () => {
setMenuPosition(null); // メニューを閉じる
};
return (
<>
<div
onContextMenu={handleContextMenu}
onClick={handleClick}
style={{
height: '300px',
border: '1px solid #ccc',
position: 'relative'
}}
>
右クリックしてメニューを表示
{menuPosition && (
<div
style={{
position: 'fixed',
left: menuPosition.x,
top: menuPosition.y,
backgroundColor: 'white',
border: '1px solid #ccc',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
padding: '5px 0',
zIndex: 1000
}}
>
<div style={{ padding: '5px 20px', cursor: 'pointer' }}>
オプション1
</div>
<div style={{ padding: '5px 20px', cursor: 'pointer' }}>
オプション2
</div>
<div style={{ padding: '5px 20px', cursor: 'pointer' }}>
オプション3
</div>
</div>
)}
</div>
</>
);
}
パターン3:マウスフォロー要素
import { useState, useEffect } from 'react';
function MouseFollower() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return (
<div
style={{
position: 'fixed',
left: position.x,
top: position.y,
width: '20px',
height: '20px',
backgroundColor: '#4CAF50',
borderRadius: '50%',
pointerEvents: 'none',
transform: 'translate(-50%, -50%)',
transition: 'all 0.1s',
zIndex: 9999
}}
/>
);
}
よくあるトラブルと解決法
マウスイベントでよくある問題を見てみましょう。
問題1:イベントが発火しない
原因:
親要素にイベントを設定しているが、子要素でstopPropagationされている可能性があります。
解決策:
// 問題のあるコード
<div onClick={() => console.log('親')}>
<button onClick={(e) => e.stopPropagation()}>
クリックしても親に伝わらない
</button>
</div>
// 解決策: キャプチャフェーズで捕捉
<div onClickCapture={() => console.log('親(キャプチャ)')}>
<button onClick={(e) => e.stopPropagation()}>
これなら親も反応する
</button>
</div>
問題2:ホバー時のちらつき
原因:
子要素にマウスが入ると、親のonMouseLeaveが発火してしまう。
解決策:
onMouseOver/onMouseOutの代わりにonMouseEnter/onMouseLeaveを使いましょう。
// 良い例
<div
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<span>子要素があってもOK</span>
</div>
問題3:ドラッグ中にテキストが選択される
原因:
ドラッグ中にテキスト選択が起きてしまう。
解決策:
<div
onMouseDown={handleMouseDown}
style={{
userSelect: 'none', // テキスト選択を無効化
WebkitUserSelect: 'none',
MozUserSelect: 'none'
}}
>
ドラッグ可能な要素
</div>
問題4:モバイルで動作しない
原因:
マウスイベントは、タッチデバイスでは動作が異なります。
解決策:
タッチイベントも併用しましょう。
function TouchAndMouseButton() {
const [isPressed, setIsPressed] = useState(false);
const handleStart = () => setIsPressed(true);
const handleEnd = () => setIsPressed(false);
return (
<button
onMouseDown={handleStart}
onMouseUp={handleEnd}
onTouchStart={handleStart}
onTouchEnd={handleEnd}
style={{
backgroundColor: isPressed ? '#4CAF50' : '#ddd',
padding: '20px'
}}
>
押してみて
</button>
);
}
よくある質問
Q: onClickとonMouseDownの違いは?
A: onClickはクリック動作全体(押して離す)で発火しますが、onMouseDownはボタンを押した瞬間だけで発火します。ドラッグ操作を実装する場合はonMouseDownを使いますね。
Q: イベントハンドラはインラインで書くべき?それとも別関数に?
A: 簡単な処理ならインラインでもOKですが、複雑な処理や再利用する場合は別関数に切り出すことをおすすめします。パフォーマンス最適化の観点からも、useCallbackと組み合わせて別関数にする方が良いですよ。
Q: 合成イベントとネイティブイベントの違いは?
A: React の合成イベントはブラウザの違いを吸収してくれるラッパーです。基本的には合成イベントを使えば問題ありませんが、特殊な操作が必要な場合のみevent.nativeEventでネイティブイベントにアクセスします。
Q: マウスイベントとタッチイベント、どちらを使うべき?
A: デスクトップ専用ならマウスイベントでOKですが、モバイル対応が必要なら両方をサポートするか、ポインターイベント(onPointerDownなど)を使うことをおすすめします。
Q: preventDefaultとstopPropagationの使い分けは?
A: preventDefaultはブラウザのデフォルト動作(リンク遷移、フォーム送信など)を防ぎ、stopPropagationはイベントの親要素への伝播を止めます。目的に応じて使い分けましょう。
まとめ:マウスイベントを使いこなそう
Reactのマウスイベントについて、重要なポイントをおさらいします。
今日学んだこと:
- Reactではキャメルケース(onClick)でイベントを書く
- 主要イベント:onClick、onMouseEnter、onMouseLeave、onMouseMove
- イベントオブジェクトで座標や修飾キーを取得できる
- ホバーエフェクトは状態管理で実装
- ドラッグはonMouseDown/Move/Upの組み合わせ
- 合成イベントがクロスブラウザ対応を担当
- preventDefault/stopPropagationで動作を制御
- useCallbackやスロットリングでパフォーマンス最適化
- TypeScriptでは適切な型指定が重要
マウスイベントは、インタラクティブなUIを作る上で欠かせない機能です。
最初は種類が多くて混乱するかもしれませんが、実際に手を動かして試してみることが一番の近道ですよ。クリック、ホバー、ドラッグと、徐々に複雑な操作にチャレンジしていけば、自然と使いこなせるようになります。
ユーザーにとって快適で直感的なインターフェースを作るために、ぜひ今日学んだ知識を活かしてみてくださいね!
関連記事:
- Reactのイベントハンドリング完全ガイド
- useStateとuseEffectの使い方
- Reactのパフォーマンス最適化テクニック


コメント