useWheel
要素の上のホイールイベントを、キャンセルできるリスナで購読します。
SourceLoading...
function App() { const [count, setCount] = useState(0) const [deltaY, setDeltaY] = useState(0) const wheelRef = useWheel<HTMLDivElement>((event) => { // The listener is not passive, so the page can be kept from scrolling. event.preventDefault() setDeltaY(event.deltaY) setCount((count) => toFixed(count - event.deltaY * 0.01, 2)) }) return ( <div style={{ userSelect: 'none' }}> <div ref={wheelRef} style={{ padding: '2rem', marginBottom: '0.5rem', border: '1px solid currentColor', borderRadius: 8, textAlign: 'center', }} > Wheel here </div> <div>count: {count}</div> <div>deltaY: {deltaY}</div> </div> ) }
インポート
import { useWheel } from '@tremolo-ui/react'
onWheel ではいけない理由
リスナは passive: false で登録されます。 React は自前のホイールリスナを passive で付けるので、onWheel の中の preventDefault() は何もせず、コントロールの下でページがスクロールしてしまいます。ノブがページを動かさずにホイールへ反応できるのは、この hook があるからです。
ハンドラはオプションではなく第 1 引数で、イベントのたびに読み直されます。インラインのアロー関数で書いてもリスナは付け直されません。
ページのスクロールを奪わない
ホバーしただけでホイールに反応するコントロールはページのスクロールを奪うので、長いフォームの中で通り過ぎるだけで値が黙って変わります。requireFocus は、それを明示的な操作にします。
const ref = useWheel(handler, { requireFocus: true })
判定は同一性ではなく contains です。実際にフォーカスを取るのは thumb や <input> のような子孫であることが多いためです。
共有する要素で購読する
ホイールイベントはカーソルの下にある要素にしか届かないので、複数の可動パートからなるコントロールは、リスナを持つパートの上にカーソルがあるときしか反応しません。target を使うと共有する要素で購読でき、どのパートが反応するかをそれぞれが決められます。
useWheel(handler, { target: trackRef })
target を渡したとき、返される ref callback は使いません。
オプション
ハンドラは第 1 引数です。以下は第 2 引数のものです。
| Prop | 型 | 既定値 |
|---|---|---|
requireFocus | boolean | false |
target | RefObject<Element | null> | — |
すべての説明: UseWheelOptions