行銷展示區塊

痛點圍攏

在輸入框打字時,周圍的痛點卡片依序向中心靠攏、越來越擁擠,送出後一起散開消失,適合在行銷頁呈現「零散工具帶來的困擾」。

行銷頁開場用的互動區塊:中央是一個真的能打字的輸入框,周圍 6–10 張痛點小卡分布在橢圓軌道上。字數越多、壓力越大,卡片依陣列順序一張張往中心靠攏、輕微傾斜縮小並彼此重疊;字數到達 pressureChars 時整群做微幅擠壓呼吸。按 Enter 或送出鈕後,卡片沿各自角度向外飛散淡出,中央換成你提供的 resolved 結果節點。開啟 autoPlay 可自動模擬逐字打字並循環播放,使用者一點輸入框就交還控制權。窄螢幕自動改為輸入框上下兩排堆疊。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add pain-point-orbit

還沒有專案,或安裝遇到問題?看快速開始 →

Playground

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

12
160
編輯中的呼叫預設屬性
<PainPointOrbit/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

安裝

npx webberui@latest add https://webberui.com/r/pain-point-orbit.json

或在 components.json 設定 registries 後,改用 @webberui/pain-point-orbit 安裝。

使用

import { Mail, Sheet, CalendarClock } from "lucide-react";
import { PainPointOrbit } from "@/components/ui/pain-point-orbit";

<PainPointOrbit
  points={[
    { id: "quote", text: "報價單在信箱裡找不到", icon: <Mail /> },
    { id: "sheets", text: "客戶資料散在三個試算表", icon: <Sheet /> },
    { id: "weekly", text: "每週手動彙整週報", icon: <CalendarClock /> },
  ]}
  placeholder="今天想先處理什麼?"
  autoPlay
  demoText="幫我整理所有客戶的最新報價"
  resolved={<div className="rounded-xl border px-4 py-3">全部集中在一個工作台</div>}
  onSubmit={(value) => console.log(value)}
/>

Props

Prop型別預設值說明
pointsPainPoint[]—環繞輸入框的痛點卡(建議 6–10 張),依陣列順序逐張向中心靠攏
valuestring—受控輸入值;未提供時由元件內部管理(非受控)
defaultValuestring""非受控模式的初始輸入值
onValueChange(value: string) => void—輸入值變動時回呼(含 autoPlay 模擬打字)
onSubmit(value: string) => void—按 Enter 或送出鈕時回呼,參數為目前輸入值
onReset() => void—點「重新開始」讓卡片回到外圈時回呼
placeholderstring"今天想先處理什麼?"輸入框佔位文字
labelstring"想先處理的事"輸入框的無障礙標籤(視覺上隱藏)
submitLabelstring"送出"送出鈕的無障礙標籤
resetLabelstring"重新開始"重新開始按鈕文字
pressureCharsnumber24字數達到此值時壓力為 1(所有卡片都已靠攏)
autoPlaybooleanfalse是否自動模擬逐字打出 demoText、送出、再重來;使用者一互動就停止
demoTextstring""autoPlay 模擬打字的內容
resolvedReactNode—送出後在中央淡入的結果節點;未提供時顯示預設結果卡
accentstring—痛點卡左緣色條顏色;未提供時使用 --wb-pain-orbit-accent(預設 #f43f5e)
stiffnessnumber160卡片位移彈簧的剛度,數值越大靠攏節奏越快
outerRadiusnumber0.42外圈半徑,佔容器寬(高)的比例
innerRadiusnumber0.26靠攏後的內圈半徑,佔容器寬(高)的比例
classNamestring—透傳到最外層容器

PainPoint

欄位型別預設值說明
idstring—唯一識別,用作 React key
textstring—一句痛點描述
iconReactNode—卡片左側圖示;未提供時顯示預設的警示圖示

細節

  • 壓力計算:pressure = min(1, 字數 / pressureChars),字數以字元(code point)計,中文一個字算一個。第 k 張卡(從 0 起算)在 pressure > k / N 時啟動,往內圈移動、旋轉 ±3° 內的確定性角度、縮小到 0.92,並依啟動順序疊上 z-index。
  • 卡片初始角度均分並加上以 index 算出的確定性小偏移(不使用亂數,SSR 與 client 一致);軌道半徑會夾在容器內,卡片不會超出邊界被裁切。
  • 卡片位移是 spring(阻尼固定 18,stiffness 可調);送出時卡片沿各自角度飛散,每張錯開 40ms。
  • 節奏 CSS 變數:--wb-pain-orbit-scatter(散開時長,預設 400ms)、--wb-pain-orbit-breathe(擁擠呼吸週期,預設 1.5s)、--wb-pain-orbit-accent(左緣色條,預設 #f43f5e)。在外層或全域覆寫即可。
  • Enter 送出有 IME 組字保護:中文注音等輸入法組字中按 Enter 不會誤送出,並處理 Safari 在 compositionend 後才發 keydown 的情況。
  • autoPlay 會呼叫 onValueChange 與 onSubmit;使用者聚焦、點擊或輸入時立即停止,元件離開視窗時暫停計時器。
  • 寬度小於 640px(Tailwind sm 斷點)時改為輸入框上下兩排堆疊。

可及性

  • 輸入框以視覺隱藏的 label 關聯;送出鈕為 type="button" 並帶 aria-label,輸入為空時停用
  • 痛點卡群為 role="list",每張卡為 listitem;卡片不可聚焦,不搶鍵盤焦點
  • 送出結果透過 aria-live="polite" 區域朗讀;使用者送出後焦點移到「重新開始」,重來後回到輸入框
  • 所有按鈕有 focus-visible 外框
  • 使用者系統開啟「減少動態效果」時,卡片不移動、不呼吸,只隨壓力改變透明度與邊框色;送出時直接淡出