
2026/08/26 1:35
ツールチップには遅延が必要で、その後にスキップする必要がある
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本記事は、「warm page」というUX パターンを紹介し、一般的なツールチップのタイミング問題に対する洗練された解決策として提案している。以前、開発者たちはゼロ遅延のツールチップは瞬間的ではあるが高速な移動時にカクつきを引き起こす一方、固定遅延は操作を重く感じさせるというジレンマに直面していた。この新しいアプローチは、実際のユーザーの動き速度に基づいて表示・非表示の遅延を動的に調整することで、レスポンシブネスと滑らかなパフォーマンスのバランスをとる。React の
useRef を共有ステートのために利用し、高コストな画面更新をトリガーすることなくデータを管理することで、ロゴなど隣接する要素間でカーソルが素早く移動した際に意図しないトリガーを防いでいる。提供された動画では、この改善を示すために、もろい元の挙動と磨かれた結果を対比させている。AI ツールはこのような機能のための機能性のコードを生成することはできるが、これらの微妙なインタラクションの詳細を質のために洗練するには人間の洞察が必要不可欠である。また、チームは特定の監査スキルを使用してプロジェクト内で類似の問題を発見することもできるし、完全に JavaScript を使用しない場合は transition-delay を使用する純粋な CSS バージョンを実装することも望ましい。著者は明確に述べているが、ここで示された観点は Coinbase のそれとは異なるものである。本文
ブラウザにおける重要なマイクロインタラクション:ツールチップの「ウォームアップ」パターン解説
問題的背景と課題
製品開発において、質問一覧ページの企業ロゴにホバーすると企業名を表示するツールチップを実装していますが、以下の現象に悩まされていました。
- 問題点: カーソルを移動させるだけで、軌跡上にある要素に瞬間的にツールチップが表示されてしまう。
- 原因: ツールチップの表示遅延(Delay)を
に設定していたためである。0ms - 試行錯誤: 解決のために
の遅延を追加したが、これが新たな UX 問題を招いた。200ms- 同じ質問に複数の企業が表示される場合、ロゴ間を移動すると毎回遅延が発生する。
- これにより操作が重く感じられ、ユーザーエクスペリエンス(UX)が低下した。
解決策:2 つのタイムゾーンと状態管理
本稿では、ブラウザのツールバーなどに見られる微小だが重要なインタラクションパターンを解説します。以下の 3 つのステップで問題が解決されます。
- 開示遅延: ユーザーがロゴにホバーすると、ツールチップは開くまで 200ms 待機する。
- ウォームウィンドウ(Warm Window): ツールチップが閉じると、300ms の「ウォーム」状態が始まる。この間に次のホバーがあった場合、待機なしで即座に表示する。
- このタイマーが終了すると、ページは再び「コールド」状態に戻る。
- ステップがないと、最初のツールチップによる遅延設定が永続的に無効化される。
フローの概要
| 状態遷移 | アクション | 結果 | ページの状態 |
|---|---|---|---|
| ホバー | が発火 | 200ms 待機 → ツールチップ開く | **「ウォーム」**へ移行 |
| 移動(コールド時) | クールダウン中に別のロゴへ移動 | 即座にツールチップが開く | 「ウォーム」状態維持 |
| 移動(コールド後) | クールダウン終了して再度ホバー | 200ms 待機 → ツールチップ開く | **「コールド」**に戻る |
[ホバー] ↓ 200ms 待機 [ツールチップが開く] (ページは「ウォーム」状態へ) ↓ カーソルを離す [ツールチップが閉じる] ↓ 300ms のクールダウン期間に入る ├─ クールダウン中に別のホバー → 即座に開く(ウォーム維持) └─ クールダウン終了 → ページは「コールド」に戻り、200ms 待機再開
コードの実装方法(React による実例)
当サイトでは Radix と Motion を使用してツールチップを構築していますが、以下のシンプルな React バージョンでこのパターンをデモします。
ステップ 1: ロゴにホバーした場合 (onMouseEnter
)
onMouseEnterロゴ上でイベントが発火すると、以下の関数が呼び出されます。コンポーネントはまず「ページはウォームか?」と確認し、ウォームなら即座に表示します。そうでなければタイマーを起動します。
function handleEnter() { // `tooltips` は useContext 変数で、コンポーネント間で `isWarm` 状態を共有しています。 if (tooltips.isWarm) { show(); return; } // `openTimer` は useRef で保持されており、`.current` で値を設定・アクセスします。 openTimer.current = setTimeout(show, tooltips.openDelay); }
ステップ 2: ツールチップが開く場合 (show
関数)
showツールチップの開閉を処理する
show 関数は以下の処理を行います。
の値を状態変数tooltips.isWarm
にコピーします(CSS アニメーションスキップ用)。instant- ツールチップを開きます (
).setOpen(true) - プロバイダーに「ツールチップが開いている」と通知し、ページを「ウォーム」状態とマークします。
function show() { setInstant(tooltips.isWarm); setOpen(true); tooltips.markOpened(); }
CSS との連携
instant フラグ(データ属性)を付与することで、ページがウォーム状態の場合はアニメーションなしで即座に表示します。
/* アニメーションをスキップするスタイル */ .tooltip[data-instant="true"] { transition-duration: 0ms; }
ステップ 3: ロゴから離れる場合 (onMouseLeave
)
onMouseLeaveロゴ上でイベントが発火した際、以下の処理を行います。
- 起動されたタイマーを停止 (
)。clearTimeout - ツールチップが開いていない場合は即座に返却(閉じる也不需要)。
- ツールチップが開いている場合のみ、閉じてプロバイダーに通知し、クールダウンタイマーを開始する。
function handleLeave() { clearTimeout(openTimer.current); // もしツールチップが開いていないなら、何もしない if (!open) return; setOpen(false); setInstant(false); // クールダウン期間を開始(プロバイダー側で行う) tooltips.markClosed(); }
注意点: カーソルがロゴを横切る時間が遅延時間(200ms)より短い場合、タイマーは破棄されツールチップは表示されません。この挙動により、誤って開いてしまうのを防ぎます。
ステップ 4: プロバイダーの共有状態 (TooltipProvider
)
TooltipProvider全体のパターンを機能させるための共有状態(Ref を使用)です。ページは「ウォーム」か「コールド」かのいずれかを保持します。
const warm = useRef(false); const cooldownTimer = useRef(null); // クールダウンタイマーのクリーンアップ処理 useEffect(() => () => clearTimeout(cooldownTimer.current), []); const tooltips = useMemo( () => ({ openDelay, // 開示遅延 (例: 200ms) // ページがウォームかどうかを判定する(React の状態ではない Ref) isWarm: () => skipWhenWarm && warm.current, // ツールチップが開かれた通知 markOpened() { warm.current = true; clearTimeout(cooldownTimer.current); // 進行中のクールダウンを破棄 }, // ツールチップが閉じられた通知 markClosed() { clearTimeout(cooldownTimer.current); cooldownTimer.current = setTimeout(() => { warm.current = false; }, warmFor); // クールダウン期間(例: 300ms) }, }), [openDelay, warmFor, skipWhenWarm] );
重要ポイント:
は React の状態ではなく Ref です。これにより、ツールチップの再レンダリングを防ぎ、パフォーマンスを維持できます。isWarm
ではクールダウンタイマーを破棄することで、連続した移動(ウォーム状態)を実現しています。markOpened
タイマーの数値設定について
最適な数値は以下のバランスから決定されます。
| 数値 | 理由 |
|---|---|
| 開示遅延: 200ms | 150ms 未満だと誤発火を防げず、250ms 超えると操作が不自然になるため。バランスが良い。 |
| ウォームウィンドウ: 300ms | 次のトリガーへの移動をカバーしつつ、数秒後のホバーにも待機させるための長さ。 |
| 閉じる遅延: 0ms | カードルから離れる行為は明確であり、待つ必要がないため。 |
判断力と感性の重要性
AI にツールチップの作成を依頼しても、機能面は完備します。しかし、以下の点を理解してください。
- 品質の違い: 作動する製品と洗練された製品の差は、細部の挙動(アニメーションやタイミング)に現れます。
- 人間の役割: AI から適切なガイドが必要であり、最終的なリリース判断は人間が行う責任があります。
- スキルの活用: 色、アクセシビリティ、タイポグラフィなど、他のエンジニアのスキルを取り入れることで、ご自身の判断力を発展させることができます。
「堅実な出力」は生成できますが、「本番環境で利用可能か」という品質感は、経験や感性によって決定されます。
紹介する Claude スキル
直面した問題を解決するための Claude スキル を提供します。以下のコードをコピーして、ご自身のコードベースの監査にご利用ください。
名前: tooltip
説明: 意図しないカーソルの移動でツールチップが開かれるのを防ぐため、遅延とウォーム状態のロジックを実装する。
スキル詳細:ツールチップのタイミング
クリックは常に意図的行為ですが、ホバーはそうではありません。 カーソルは画面を移動しながら必要な場所へ行き、その過程で要素の上を通過するため、 ホバー単独ではユーザーがツールチップを開きたいかどうかを判断できません。 この 200ms が、その判断基準となります。 ```css .tooltip { transition-delay: 200ms; }
**本記事の表現は著者の見解であり、Coinbase の立場を反映するものではありません。**