WebberUI

Draggable Snap Button

可拖曳並回彈吸附的按鈕:拖到哪、放開後就以 spring 吸附到最近的落點,支援多段落點、方向鍵切換與純回彈模式。

載入預覽⋯

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

0.16
<DraggableSnapButton />

安裝

npx shadcn@latest add https://webberui.com/r/draggable-snap-button.json

或在 components.json 設定 registries 後,改用 @webberui/draggable-snap-button 安裝。

安裝依賴後,從 registry JSON(/r/draggable-snap-button.jsonfiles[0].content)複製 draggable-snap-button.tsx 原始碼到你的 components/ui/ 目錄:

npm install motion clsx tailwind-merge

使用

import {
  DraggableSnapButton,
  type SnapPoint,
} from "@/components/ui/draggable-snap-button";

const modes: SnapPoint[] = [
  { x: -100, label: "省電" },
  { x: 0, label: "均衡" },
  { x: 100, label: "效能" },
];

<DraggableSnapButton
  axis="x"
  snapPoints={modes}
  defaultIndex={1}
  onSnapChange={(index, point) => console.log(index, point.label)}
>
  拖曳
</DraggableSnapButton>

不傳 snapPoints 時只有原點一個落點,就是純回彈按鈕——拖到哪、放開都彈回原位:

<DraggableSnapButton dragConstraints={boxRef} dragElastic={0.25}>
  拋看看
</DraggableSnapButton>

Props

Prop型別預設值說明
snapPointsSnapPoint[][{ x: 0, y: 0 }]落點清單;放開後吸附到最近的一點。預設只有原點即純回彈
axis'both' | 'x' | 'y''both'限制拖曳方向:自由、僅水平、僅垂直
indexnumber受控目前落點索引;提供時由外部主導位置
defaultIndexnumber0非受控初始落點索引
onSnapChange(index: number, point: SnapPoint) => void吸附落定到「不同」落點時觸發
springConfigSpringOptions{ stiffness: 520, damping: 30, mass: 0.9 }回彈吸附的 spring 參數
dragElasticnumber | boolean0.16拖出邊界時的橡皮筋彈性,需搭配 dragConstraints
dragConstraintsRefObject | { top, left, right, bottom }拖曳邊界,透傳給 motion
classNamestring覆蓋預設樣式
childrenReactNode按鈕內容

SnapPoint{ x?: number; y?: number; label?: string }xy 是相對按鈕原始位置的位移(px),省略的軸視為 0;label 供輔助科技朗讀。其餘 <button> 屬性(disabledaria-label 等)皆直接透傳。

細節

  • 拖曳與吸附:以 motion 的 drag 承載位置,dragMomentum 關閉讓落點計算穩定;放開時只計入可拖曳的軸,找出距離最近的落點並以 spring 吸附過去
  • 純回彈snapPoints 只有原點時,任何方向拖曳放開都彈回原位;搭配 dragConstraintsdragElastic 可得到拖出邊界的橡皮筋手感
  • 受控/非受控:提供 index 進入受控模式,位置由外部主導、onSnapChange 只作為請求;否則以 defaultIndex 起始並自行記錄落點
  • 落定通知:只有吸附到「不同」於前一個的落點才會觸發 onSnapChange,回彈到原落點不重複發送
  • 鍵盤支援:多落點時方向鍵(←↑ 上一段、→↓ 下一段)、HomeEnd 可在落點間切換,並攔下方向鍵的頁面捲動
  • 無障礙:單軸多落點時補上 role="slider"aria-valuemin/max/now/valuetext,等同滑桿語意;另以 aria-describedby 掛上拖曳說明、aria-live="polite" 宣告目前落點
  • 使用者系統開啟「減少動態效果」時:吸附仍完整發生(位置照樣移動,功能不變),僅把 spring 換成極短的線性補間,並移除拖曳時的縮放
  • touch-none + select-none 避免觸控拖曳時觸發捲動與選字;卸載時會停掉進行中的 spring,避免動畫回呼在卸載後才觸發

On this page