メインコンテンツまでスキップ

useWheel

要素の上のホイールイベントを、キャンセルできるリスナで購読します。

Source
Loading...
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型既定値
requireFocusbooleanfalse
targetRefObject<Element | null>—

すべての説明: UseWheelOptions