🎰
onWheelが終わった直後の処理を書くときのTips
結論
onWheel
が終了した直後の処理を書くにはsetTimeout
とclearTimeout
を使えばいい感じに実装できる
下記Gifはちょっとわかりづらいかもしれないが、wheelしている間はdeltaX(横方向への移動した量)をconsoleに出力しつつ、wheelが終わったらwheelが終了したよ
をconsoleに出力している
はじめに
とあるプロダクトと同じような機能を実装するタスクがあり、その機能を実装するためには横にwheelした後に処理を入れる必要があったが、WheelEvent
にそれらしいプロパティはなく、wheelが終了した後にどのように処理を入れるかわからず詰まった。
とあるプロダクト上でChromeのDevToolsを開き、ビルドされた後のjsと睨めっこして、wheelが終了した後にどのように処理を入れてるかを解読したら結構参考になったのでまとめることにした。
記事のターゲット
- wheelイベントが終了したときに何か処理をしたい人
実装したコード
コード全容
import { useEffect, useState, WheelEvent } from 'react'
export const Test = (): JSX.Element => {
const [wheelEndTimeout, setWheelEndTimeout] = useState<
NodeJS.Timeout | undefined
>(undefined)
const [isWheel, setIsWheel] = useState(false)
useEffect(() => {
const clear = () => {
if (wheelEndTimeout) {
clearTimeout(wheelEndTimeout)
}
}
return clear()
}, [])
const handleWheel = (e: WheelEvent<HTMLDivElement>) => {
console.log(e.deltaX)
setIsWheel(true)
if (wheelEndTimeout) {
clearTimeout(wheelEndTimeout)
setWheelEndTimeout(undefined)
}
if (isWheel) {
const wheelEndTimeoutId = setTimeout(() => {
setIsWheel(false)
console.log('wheelが終了したよ')
// NOTE: ここにwheel終了後の処理を書く
}, 100)
setWheelEndTimeout(wheelEndTimeoutId)
}
}
return (
<div
className="flex items-center justify-center w-96 h-screen bg-primary"
onWheel={handleWheel}
>
<p>ここをホイールしてみて</p>
</div>
)
}
wheelイベントが終了した後にどのように処理を入れるか
- wheel中かどうかをコンポーネントのstateに保持する
- wheel中にsetTimeoutが呼び出されている場合はclearTimeoutする
- wheel中の場合は100ms後にwheel中かどうかのstateをfalseにするsetTimeoutを呼ぶ(setTimeoutのなかにwheel終了後の処理をかく)
ざっくり言うとsetTimeoutの中にwheelイベント終了時の処理を定義し、wheelイベントが発火されるたびにsetTimeoutの時間を100ms秒伸ばしてます
const [wheelEndTimeout, setWheelEndTimeout] = useState<
NodeJS.Timeout | undefined
>(undefined) // setTimeoutを識別するためのidを保持
const [isWheel, setIsWheel] = useState(false) // wheel中かどうかの状態を保持
const handleWheel = (e: WheelEvent<HTMLDivElement>) => {
console.log(e.deltaX)
setIsWheel(true) // onWheel中のフラグを立てる
if (wheelEndTimeout) {
clearTimeout(wheelEndTimeout) // すでにsetTimeoutで100ms後にonWheel終了するようにしていたらsetTimeoutをclearする
setWheelEndTimeout(undefined)
}
if (isWheel) {
const wheelEndTimeoutId = setTimeout(() => {
setIsWheel(false) // wheel中であれば100ms後にwheelが終了するフラグを立てる
console.log('wheelが終了したよ')
// NOTE: ここにwheel終了後の処理を書く
}, 100)
setWheelEndTimeout(wheelEndTimeoutId)
}
}
念のため、アンマウント時にsetTimeoutが生きていた場合はclearするようにuseEffectで定義しておく
useEffect(() => {
const clear = () => {
if (wheelEndTimeout) {
clearTimeout(wheelEndTimeout)
}
}
return clear()
}, [])
所感
wheelイベントやscrollイベントなど、そのままのイベントプロパティでは終了した状態を把握するのが難しい場合に、setTimeoutとclearTimeoutを駆使して終了時の処理を書くのは手法の一つとして良さげな気がした
Discussion