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

useDragValue

ポインタのドラッグで数値を動かします。範囲・step・スケールをこの hook が持つので、ハンドラが受け取るのはピクセルではなく値です。

Source

Knob / Slider / XYPad はこの上に作られています。自作のコントロールに同じドラッグの挙動が要るときに使ってください。

Loading...
function App() {
  const areaRef = useRef<HTMLDivElement>(null)
  const [value, setValue] = useState<[number, number]>([50, 50])

  const { refCallback, dragging } = useDragValue<HTMLDivElement>({
    // y is reversed so that its maximum is at the top.
    axis: [
      { min: 0, max: 100, step: 1 },
      { min: 0, max: 100, step: 1, reverse: true },
    ],
    baseElementRef: areaRef,
    updateOnPointerDown: true,
    onChange: (value) => setValue(value),
  })

  // The element that starts the drag is also the one the pointer is measured
  // against, so it takes both refs.
  const ref = useComposedRefs<HTMLDivElement>(areaRef, refCallback)

  return (
    <div style={{ userSelect: 'none' }}>
      <div
        ref={ref}
        style={{
          position: 'relative',
          width: '100%',
          height: 160,
          marginBottom: '0.5rem',
          border: '1px solid currentColor',
          borderRadius: 8,
          touchAction: 'none',
        }}
      >
        <div
          style={{
            position: 'absolute',
            left: `${value[0]}%`,
            top: `${100 - value[1]}%`,
            width: 16,
            height: 16,
            borderRadius: '50%',
            background: 'currentColor',
            translate: '-50% -50%',
            pointerEvents: 'none',
          }}
        />
      </div>
      <div>dragging: {String(dragging)}</div>
      <div>
        x: {value[0]}, y: {value[1]}
      </div>
    </div>
  )
}

インポート​

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

値を持つのは呼び出し側​

この hook は値を持ちません。 保存するのは onChange で、現在の値は getValue から読み直します。baseElementRef のときはそもそも読みません。ポインタの位置が値そのものだからです。同じ値をテキストフィールドからも MIDI メッセージからもプリセットからも設定でき、ドラッグと state がずれない理由がこれです。

ドラッグの 2 つのマッピング​

どちらになるかは渡したオプションで決まります。ちょうど一方だけを渡してください。

baseElementRef: 値は「指している位置」​

ポインタを要素の bounding rect に対して正規化するので、値はポインタにぴったり追随します。updateOnPointerDown を付けると、ポインタが動く前に押した位置へ値が飛びます。Slider と XYPad の挙動です。

getValue: 値は「動かした距離」だけ動く​

ポインタの位置には意味がありません。値は元の位置から離れていき、pixelRange ピクセルが範囲全体に相当します。ノブがドラッグに追随するときの挙動で、値がポインタへ飛ばないのはこのためです。

Loading...
function App() {
  const [value, setValue] = useState(50)

  const { refCallback, dragging } = useDragValue<HTMLDivElement>({
    axis: { min: 0, max: 100, reverse: true },
    // The value moves away from where it stood, so the hook has to be able to
    // read it. 200px of movement covers the whole range.
    getValue: () => [0, value],
    pixelRange: 200,
    // Hold shift to cover a tenth of the range with the same movement.
    sensitivity: (state) => (state.event.shiftKey ? 0.1 : 1),
    cursor: 'ns-resize',
    onChange: ([, y]) => setValue(y),
  })

  return (
    <div style={{ userSelect: 'none' }}>
      <div
        ref={refCallback}
        style={{
          padding: '2rem',
          marginBottom: '0.5rem',
          border: '1px solid currentColor',
          borderRadius: 8,
          textAlign: 'center',
          touchAction: 'none',
        }}
      >
        Drag up and down
      </div>
      <div>dragging: {String(dragging)}</div>
      <div>value: {value.toFixed(1)}</div>
    </div>
  )
}

微調整​

sensitivity は動くたびに読まれるので、ドラッグの途中で押された修飾キーにも答えられます。

useDragValue({
sensitivity: (state) => (state.event.shiftKey ? 0.1 : 1),
})

baseElementRef では値は普通「指している位置」なので、1 以外にするとマッピングが相対的になり、そのドラッグの間はポインタと値が離れたままになります。

軸​

axis は両方の軸に効く 1 つの AxisOptions か、軸ごとの [x, y] を取ります。min / max / step / scale のほかに reverse があり、y 軸を上向きに伸ばすのに使います。画面の y は下向きに伸びるためです。

1 軸しか動かないコントロールでも値は両軸ぶん来ます。上の相対マッピングの例のように、使わない方は無視してください。

オプション​

Prop型既定値
axis*AxisOptions | readonly [AxisOptions, AxisOptions]—
baseElementRefRefObject<Element | null>—
getValue() => XY<number>—
pixelRangenumber | readonly [number, number]100
sensitivity(state: DragState) => number—
updateOnPointerDownboolean—
thresholdnumber0
cursorstring—
pointerLockbooleanfalse
shouldStart(event: PointerEvent) => boolean—
onChange(value: XY<number>, state: DragState) => void—
onDragStart(value: XY<number>, state: DragState) => void—
onDragEnd(value: XY<number>, state: DragState) => void—

すべての説明: UseDragValueOptions