Cart Arc
拋物入袋:點擊加入購物車時商品縮圖沿貝茲弧線飛向購物車,圖示果凍擠壓、徽章數字彈跳遞增,支援連點併發飛行。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
0.72
1
0
<CartArc />
安裝
npx shadcn@latest add https://webberui.com/r/cart-arc.json或在 components.json 設定 registries 後,改用 @webberui/cart-arc 安裝。
安裝依賴後,從 registry JSON(/r/cart-arc.json 的 files[0].content)複製 cart-arc.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge lucide-react使用
CartArc 是容器兼狀態中樞,內部放入若干 CartArcItem 觸發器與一個 CartArcCart 目標。點擊觸發器時,一顆縮圖會沿貝茲弧線拋向購物車,命中後圖示擠壓、徽章數字彈跳遞增。
import { CartArc, CartArcCart, CartArcItem } from "@/components/ui/cart-arc";
<CartArc>
<div className="flex items-center justify-between">
<span>今日甜點</span>
<CartArcCart />
</div>
<CartArcItem image="/matcha.jpg">加入購物車</CartArcItem>
</CartArc>;讓縮圖從商品圖片(而非按鈕)起飛時,把 originRef 指向該圖片元素,並用 flyContent 指定飛行內容:
const thumbRef = React.useRef<HTMLDivElement>(null);
<div ref={thumbRef}>
<img src="/matcha.jpg" alt="抹茶拿鐵" />
</div>
<CartArcItem originRef={thumbRef} flyContent={<img src="/matcha.jpg" alt="" />}>
加入
</CartArcItem>;受控總數
不傳 count 時元件自行管理總數(非受控),可用 defaultCount 設定初始值、onCountChange 監聽變化。傳入 count 則切換為受控模式,由外部掌控數字。
const [count, setCount] = React.useState(0);
<CartArc count={count} onCountChange={setCount}>
{/* ... */}
</CartArc>;命令式觸發
進階情境可用 useCartArc() 於任意時機、從任意視口座標拋出縮圖:
import { useCartArc } from "@/components/ui/cart-arc";
function BuyNow() {
const { addToCart, count } = useCartArc();
return (
<button
onClick={(e) => {
const r = e.currentTarget.getBoundingClientRect();
addToCart(
{ x: r.left + r.width / 2, y: r.top + r.height / 2 },
{ image: "/matcha.jpg", quantity: 2 },
);
}}
>
立即購買({count})
</button>
);
}Props
CartArc
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
children | React.ReactNode | — | 內含 CartArcItem 與 CartArcCart |
count | number | — | 受控模式的商品總數 |
defaultCount | number | 0 | 非受控模式的初始總數 |
onCountChange | (count: number) => void | — | 總數變動時回呼 |
duration | number | 0.72 | 飛行時長(秒) |
className | string | — | 附加到容器的樣式 |
CartArcItem
透傳所有原生 button 屬性,另外提供:
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
image | string | — | 飛行縮圖的圖片網址 |
flyContent | React.ReactNode | — | 自訂飛行縮圖內容,優先於 image |
quantity | number | 1 | 加入的數量,累加到總數 |
originRef | React.RefObject<HTMLElement | null> | — | 飛行起點的參考元素,未提供時以按鈕自身為起點 |
onClick | (e) => void | — | 拋出縮圖後照常執行 |
CartArcCart
透傳所有原生 button 屬性,另外提供:
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
icon | React.ReactNode | lucide ShoppingCart | 自訂購物車圖示 |
useCartArc()
| 回傳 | 型別 | 說明 |
|---|---|---|
count | number | 目前購物車內的商品總數 |
addToCart | (origin, options?) => void | 從視口座標 origin 拋出縮圖並累加;options 支援 image、content、quantity |
細節與可及性
- 縮圖以二次貝茲曲線飛行:控制點抬高到兩端之上形成向上拋的拋物線,過程中縮小、末段淡出並帶輕微旋轉
- 目標座標於每次點擊時即時量測
getBoundingClientRect,捲動或版面變動皆能命中 - 飛行圖層以
createPortal疊在document.body最上層,帶aria-hidden與pointer-events-none,不干擾指標與輔助科技 - 連點時多顆縮圖併發飛行,同時上限 16 顆,超過時直接計數不飛以保護效能;每顆命中皆會計數,總數不遺漏
- 命中時圖示以 scaleX/scaleY 反向的果凍擠壓關鍵影格回彈,徽章數字以 spring 彈跳替換(舊數字下退、新數字上彈)
- 購物車帶
aria-label標示件數,並以aria-live="polite"區域播報總數變化 - 飛行動畫在卸載或命中回收時
stop()清理,不會在卸載後更新 - 使用者系統開啟「減少動態效果」時不飛行、不擠壓、徽章不彈跳,但加入購物車的計數行為照常運作