何につまづいたか
Inputエリアに記述されたTextを範囲選択した際に、色変更などのメニュー画面を表示させたかった。
そのメニューのボタンをクリックした際、Inputエリアのフォーカスが外れてしまい、ユーザーに改めてInputエリアをクリックさせることになってしまった。
メニューのボタンをクリックしても元のInputの状態を維持できるようにしたかった。
どうやったのか?
- onClickイベントではなくonMouseDown(or onPointerDown)を使用する
- e.preventDefault()を使用してブラウザのデフォルト挙動を防いだ
コード例
import React, { useRef, useState } from "react";
const InputWithMenu = () => {
const inputRef = useRef<HTMLInputElement>(null);
const [showMenu, setShowMenu] = useState(false);
const handleSelect = () => {
// 選択範囲がある場合にメニューを表示
const selection = window.getSelection();
if (selection?.toString()) {
setShowMenu(true);
}
};
const handleMenuClick = (e: React.MouseEvent) => {
e.preventDefault(); // Blur状態を防ぐ
console.log("メニューのボタンがクリックされました");
};
return (
<div style={{ position: "relative", padding: "20px" }}>
<input
ref={inputRef}
type="text"
placeholder="テキストを選択してください"
onSelect={handleSelect}
onBlur={() => setShowMenu(false)} // フォーカスが外れたらメニューを閉じる
/>
{showMenu && (
<div
style={{
position: "absolute",
top: "50px",
left: "10px",
background: "lightgray",
padding: "10px",
borderRadius: "5px",
}}
onMouseDown={(e) => e.preventDefault()} // メニュークリック時にフォーカスを失わせない
>
<button onPointerDown={handleMenuClick}>色変更</button>
<button onPointerDown={handleMenuClick}>削除</button>
</div>
)}
</div>
);
};
export default InputWithMenu;
なぜダメだったか?
onClickイベントのままhanldeMenuClickを使うと、ブラウザの挙動でクリックした対象にフォーカスが吸われてしまうので、コード例のinput要素の部分からフォーカスが外れてしまう。
これをonMouseDownに変更し、e.preventDefault()
を実行することで、フォーカスを失わせない様にすることができる。
onMouseDownとonPointerDownの違い
コード例では、onMouseDownを使っているが、onPointerDownを使うこともできる。
それぞれの違いを以下の表に示す。
特徴 | onMouseDown | onPointerDown |
---|---|---|
対象デバイス | マウスのみ | マウス、タッチ、ペンすべて対応 |
ブラウザ互換性 | 古いブラウザでも完全対応 | モダンブラウザで対応 |
ジェスチャー対応 | × | ○ |
イベントの統一性 | マウスに特化 | 複数デバイス間で統一的に動作 |
タッチ操作が必要なアプリケーションの場合は、onPointerDownを使用すると良い。
onBlurって?
onBlurは要素からフォーカスが外れた(失った)時に呼び出されるイベントハンドラー
その逆で、フォーカスが当たった時に呼び出されるイベントハンドラーはonFocus
onBlurはフォーカスが外れた時に呼び出されるので、例えば自動保存を行うときや入力内容の確認(例えば半角英数字のみか?)などの時に活躍する。
フォームの入力などを行う際、上手に使いこなして使い勝手の良いアプリを作れる様になりたい
最後に
onClick一辺倒でonPointerDownなどを知らなかったので、引き続き勉強しないと…です。