RAF による mousemove 最適化の要点

問題

mousemove イベントは高頻度で発火(ミリ秒単位)
毎回 DOM 操作(選択範囲レンダリング)すると性能劣化

解決策

requestAnimationFrame (RAF) で描画を間引く

let rafId: number | null = null

element.addEventListener('mousemove', (event) => {
    // 座標を取得
    const coord = getCoordinate(event)
    
    // RAF がすでにスケジュール済みならスキップ
    if (rafId !== null) return
    
    // 次フレームで描画をスケジュール
    rafId = requestAnimationFrame(() => {
        render(coord)  // 描画処理
        rafId = null
    })
})

RAF と mouseup の競合問題

RAF 実行前に mouseup が発火すると、状態が不整合になる

// ❌ 危険な実装
let start: Coord | null = null

mousemove: rafId = requestAnimationFrame(() => {
    setSelection(start, end)  // start が null の可能性
})

mouseup: start = null  // RAF 実行前にリセット

解決:イベント発火時に値をキャプチャ

mousemove: 
    const capturedStart = start
    const capturedEnd = coord
    
    rafId = requestAnimationFrame(() => {
        if (!capturedStart) return  // 防御的チェック
        setSelection(capturedStart, capturedEnd)
    })

mouseup:
    if (rafId !== null) {
        cancelAnimationFrame(rafId)  // 未実行の RAF をキャンセル
        rafId = null
    }
    start = null

重要ポイント

RAF のスキップ: rafId !== null で重複スケジュールを防ぐ
値のキャプチャ: RAF 内で参照する変数はイベント時点でキャプチャ
RAF のクリーンアップ: mouseup / mousedown で未実行の RAF をキャンセル
防御的 null チェック: RAF 内でも念のためガード

効果

描画頻度: 毎ミリ秒 → 60fps(約 16ms ごと)
不要な DOM 操作を削減
滑らかな UI 更新