Skip to main content

useWheel

Listen to wheel events on an element, with a listener that may cancel them.

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​

import { useWheel } from '@tremolo-ui/react'

Why not onWheel​

The listener is registered with passive: false. React attaches its own wheel listener passively, so preventDefault() inside onWheel does nothing and the page scrolls underneath the control. This hook is the reason a knob can answer the wheel without the page moving.

The handler is the first argument, not an option, and it is read on every event — an inline arrow function is fine and does not re-attach the listener.

Not stealing the page scroll​

A control that reacts to the wheel on hover alone takes the scroll away from the page, so passing over one in a long form silently changes its value. requireFocus makes it an explicit act:

const ref = useWheel(handler, { requireFocus: true })

The check is contains, not an identity test: the element that actually takes focus is usually a descendant, such as a thumb or an <input>.

Listening on a shared element​

A wheel event only reaches what the cursor is over, so a control made of several movable parts responds only while the cursor is over the part that holds the listener. target listens on the element they share instead, and each part decides whether it is the one to act:

useWheel(handler, { target: trackRef })

The returned ref callback is unused when target is given.

Options​

The handler is the first argument. These are the second.

PropTypeDefault
requireFocusbooleanfalse
targetRefObject<Element | null>—

Full reference: UseWheelOptions