伸縮變形 UI
這個技能產生的介面中,每個狀態變化都會 變形:分頁指示器像蠕蟲一樣伸向下一頁、按鈕擠壓成載入圓圈再膨脹回來、產品圖片從卡片浮起、縮小成晶片後落入購物車。它源自於建構 24 元件的展示(在 assets/showcase.html 中),以及過程中所發現的錯誤與設計修正。
外觀來自一個核心概念:拉伸必有原因。 元素變形是因為其各部分不一致(一個邊緣比另一個先到達)、因為快速移動(沿路徑伸長)、因為被擠壓(為保持體積而向另一側鼓起),或因為剛被撞擊(產生晃動)。迴圈式的隨機晃動顯得廉價;有原因的拉伸則展現材質感。
第 0 步,每次皆須執行:審核使用者程式碼並確認
在開始建構或編輯任何內容前,找出使用者介面中哪些部分可以變成伸縮的,並讓他們選擇。詳細資訊、判斷標準與訊息模板請參閱 references/audit.md;簡述如下:
- 找到程式碼:貼上的程式碼、附件(在 claude.ai 上,列出
/mnt/user-data/uploads)、工作目錄或倉儲、Artifact 連結(使用 artifact 工具讀取),或使用者指明的來源。若無任何內容,說明您搜尋了什麼,並提供兩個選擇:分享程式碼,或從頭建構新內容。 - 掃描它:
python scripts/audit_ui.py <路徑>會報告堆疊、現有動畫、缺少的減少動畫處理,以及每個 UI 類別的候選項目(含檔案:行號),並對應到展示元件與技術。 - 在上下文中閱讀每個匹配項目,僅保留真正候選。針對每個項目,記錄其位置、目前行為、將成為的變形描述句、工作量(小、中、大)及任何風險(元件庫標記、現有動畫庫、會遺失行動版選擇器的原生控制項、非常長的列表)。
- 優先排序:根據可見度 × 頻率 × 契合度,將重複項目分組(所有使用同一個
Button的按鈕視為一個項目),上限約十個,並列出您建議不要採用的項目及其理由。 - 以使用者的語言請求確認:編號的建議列表,然後一個邀請他們選擇編號、接受所有建議,或輸入不在列表中但希望變為伸縮的其他部分的提問。若環境提供可點擊選擇工具則使用之,否則使用純文字。在此回合結束;在收到回覆前不要進行任何編輯。
- 建構他們選擇的內容加上他們新增的部分,然後依循下方的工作流程,並逐項回報。
如果使用者已指明想要伸縮的部分,仍需進行審核:將他們的部分列在最前面,將其餘部分作為選項提供,並保持提問簡潔。
檔案
所有路徑皆相對於此技能資料夾(包含此 SKILL.md 的資料夾),而非使用者的專案。在讀取或執行時,請相對於技能資料夾解析它們。
| 路徑 | 內容 | 開啟時機 |
|---|---|---|
assets/stretchy-core.js | 彈簧引擎與輔助工具(邊緣配對、分段指示器、體積縮放、SVG 邊緣鼓起、FLIP 重排序、拋射物件) | 貼入您建構的每個頁面。讀取一次以了解輔助工具。 |
assets/starter.html | 頁面骨架:淺色/深色 token、支援泰文的可變字體、減少動畫處理、內嵌核心、一個範例元件 | 從頭開始新頁面 |
assets/showcase.html | 原始建構中的所有 24 個工作元件。每個 JS 區段以 /* ---------- tabs ---------- */ 格式的橫幅開始 | 當請求符合目錄元件時,使用 grep -n "/\* ---------- " assets/showcase.html 並僅讀取該區段及其 CSS |
references/audit.md | 如何尋找、掃描、判斷與優先排序使用者程式碼,以及確認訊息模板(泰文與英文) | 在第 0 步,每次皆須執行 |
references/techniques.md | 11 種伸縮技術的詳細說明,含程式碼與調整數值 | 在您建構未建構過的內容前 |
references/components.md | 目錄:每個元件的配方、彈簧數值與注意事項 | 當請求指名或類似目錄元件時 |
references/new-components.md | 如何為任何元件設計伸縮版本,以及 14 個不在展示中的完整設計 | 當請求超出目錄範圍時 |
scripts/audit_ui.py | 使用者程式碼的掃描器:堆疊、現有動畫,以及伸縮候選項目(含 檔案:行號) | 在第 0 步,每次皆須執行 |
scripts/check_page.py | 無頭瀏覽器檢查:JS 錯誤、手機與桌機的橫向溢出、隱藏元素攔截點擊,以及動畫幀的連絡表 | 每次建構完成後,在交付前 |
如果存在視覺設計技能(例如 frontend-design 或 ui-ux-pro-max),也請一併讀取。該技能決定頁面外觀(調色板、字體、佈局);此技能決定其動畫方式。動畫潤飾無法挽救一個外觀普通的頁面。
工作流程(使用者確認後)
- 為每個元件撰寫變形描述句。「當 觸發條件 時,形狀 A 變成 形狀 B,且因 原因 而拉伸。」例如:「當分頁被點擊時,指示器移動到該分頁,並因其前緣比後緣更硬而拉伸。」若您無法填入原因,請從下方表格中選取。這句話即為設計;其餘皆為實作。
- 選取技術 從表格中選取。大多數元件使用一個主要拉伸加上一個小的回饋晃動。
- 將靜止狀態定義為數值:寬度、高度、半徑、位置、進度 0 到 1。彈簧驅動數字,您透過
onUpdate將它們寫入style。CSS 過渡僅處理顏色與不透明度(以及短內容的淡入淡出)。 - 規劃內容。 文字與圖示在容器變形時不得重新流動或壓縮。為每個內容狀態在其最終大小提供獨立圖層,錨定(通常置中)並進行淡入淡出:快速退出(約 150 毫秒),延遲約 100–150 毫秒後進入,並帶有輕微縮放或模糊。容器的
overflow:hidden在內容成長時揭示內容。 - 建構 從
assets/starter.html開始,並內嵌核心。直接在彈簧回呼中撰寫樣式;切勿觸發每次影格的框架重新渲染。 - 無障礙與中斷處理(檢查清單如下)。
- 測試與觀察。 執行
scripts/check_page.py,然後擷取重要互動的動畫幀連絡表並實際觀察。原始建構中的加入購物車動畫「運作」但看起來廉價,直到檢查影格後才修復;修復方法請見「讓它變好看」。
拉伸的來源
| 情境 | 技術(詳細內容請見 references/techniques.md) | 範例 |
|---|---|---|
| 某物在位置間移動 | 邊緣配對:前緣與後緣各用一個彈簧,前緣較硬,根據方向交換 | 分頁、輪播點、開關、選擇晶片、單選點、步進器、下拉選單高亮、評分膠囊、側邊欄導航 |
| 容器改變大小或形狀 | 軸向分離:為寬度、高度、半徑使用獨立彈簧,硬度不同,因此一個軸領先 | 動態島、FAB 轉表單、從按鈕彈出的模態框、下拉選單、加入購物車按鈕 |
| 某物快速變窄或變寬 | 體積保持:根據速度縮放另一軸,scaleY = clamp(1 − v/k) | 傳送按鈕、搜尋欄位、下拉選單寬度 |
| 某物快速移動 | 速度拉伸:沿移動方向伸長,垂直方向變細 | 滑桿拇指、拋射的購物車項目、輪播卡片(傾斜)、工具提示 |
| 大型表面移動 | 邊緣鼓起:將表面繪製為 SVG 路徑,其邊緣控制點偏移 −velocity | 側邊欄、底部面板 |
| 部件懸掛在移動部件上 | 滯後跟隨者:較軟的彈簧追逐主彈簧;利用間隙實現傾斜或偏移 | 滑桿數值氣泡、摺疊面板內部文字 |
| 點擊或到達的回饋 | 踢擊:對處於 0 的靜止彈簧增加速度,它會晃動並安定下來 | 核取方塊、晶片、購物車、星星、徽章、數量限制抖動 |
| 物件融合或分離 | 黏稠濾鏡:模糊加 alpha 閾值,上方疊加銳利來源 | 黏稠選單 |
| 一個元素變成另一個元素 | 容器轉換:從觸發器的矩形開始,彈簧移動到目標矩形,內容淡入淡出 | 模態框、FAB、購物車拋射 |
| 變化之間需要暫停 | 透過中間形狀的序列化變形,該形狀具有意義(旋轉器、勾選標記) | 傳送按鈕、加入購物車按鈕 |
| 拖曳超過限制,或列表重排序 | 橡皮筋(將過度滾動除以約 3)和 帶彈簧的 FLIP,保留速度 | 輪播、面板、排序後的產品列表 |
引擎概述
Spring 是一個阻尼諧波振盪器,使用半隱式歐拉法以每影格 8 子步進行積分(影格 delta 上限為 1/30 秒,以防止背景分頁爆炸)。to(target) 會重新設定向而不重置速度,這使得快速點擊感覺連續而非重新開始。set(x) 跳躍動畫,drag(x, v) 跟隨指針並記錄速度,kick(v) 注入脈衝。阻尼在每次 tick 時乘以全域 bounce 因子,因此一個頁面級控制(軟 1.8、有彈性 1、伸縮 0.5)即可即時重新調整每個元件。在 prefers-reduced-motion: reduce 下,to() 會立即跳至目標並觸發 onRest,因此等待動畫的邏輯仍會執行。
阻尼比 ζ = c / (2√k)。UI 狀態變化在 ζ 0.6–0.8 時感覺合適;趣味回饋在 0.3–0.5;大於 1 則無過衝。展示中使用的預設值:
| 角色 | k | c | 備註 |
|---|---|---|---|
| 邊緣配對的前緣 | 420–520 | 30–34 | 先到達 |
| 後緣 | 140–170 | 17–19 | 滯後,產生拉伸 |
| 容器寬度(變形) | 240–320 | 18–24 | |
| 容器高度(變形) | 150–170 | 15–17 | 比寬度慢,因此形狀先橫向拉伸 |
| 指針跟隨者(滑桿) | 650 | 42 | 緊密,但仍保有速度感 |
| 滯後跟隨者(氣泡) | 150 | 11 | 故意鬆散 |
| 踢擊回饋 | 380–420 | 12–16 | 踢擊強度 6–10 |
| 可見過衝的高度(摺疊面板) | 210 | 14 |
經驗法則
- 限制所有變形。 控制:體積保持在 ±7–15% 內。小型快速物體(拋射晶片、滑桿拇指):最多拉伸 35–70%。始終將大小限制為正最小值,因為低阻尼彈簧可能下衝低於零。
- 依方向交換剛度。 在邊緣配對中,面向移動方向的邊緣使用較硬彈簧。若不交換,指示器會正確地向一個方向拉伸,但反向時壓縮。
- 回程時採用相反時序。 如果打開時寬度領先高度,關閉時讓高度領先寬度。形狀會沿不同路徑返回,讀起來像是彈性而非回放。
- 狀態變化用彈簧,形狀重要的路徑用基於時間的曲線。 拋射物體需要可預測的弧線(二次貝茲曲線、ease-in-out 進度,約 800 毫秒)。獨立的 x/y 彈簧會產生飄忽路徑。兩種情況下都從影格間速度推導拉伸。
- 保持內容直立。 要沿任意方向拉伸某物而不旋轉其圖片或文字,使用
rotate(a) scale(1+s, 1−s·0.45) rotate(−a)。 - 每個元件一個大膽動畫。 元件的其餘部分保持靜態:顏色變化、短暫淡出。
- 閒置時平靜。 無物自行晃動。最多在頁面載入時有一個編排好的時刻(展示在 900 毫秒後開啟動態島一次)。
讓它變好看(建構經驗)
第一個加入購物車版本將一個純色球體沿兩個獨立彈簧飛行,將按鈕縮成一個空橢圓並壓扁購物車。使用者說它不美觀。修復方法具有通用性:
- 移動真實物體。 複製產品圖片,稍提高並加上陰影,然後將其縮小為一個仍顯示圖片的圓形晶片,同時飛行。抽象圓點讀起來像除錯輸出。
- 中間形狀必須具有意義。 縮小的按鈕是空團塊,除非它顯示內容:旋轉器、自繪勾選標記、計數。加入按鈕現在變成一個帶勾選的綠色圓圈,然後成長為 − n + 步進器。
- 控制項上的微妙體積。 按鈕上 ±18% 的垂直鼓起看起來像藥丸形雞蛋。±7% 讀起來像柔軟材質。
- 到達需要接收者。 購物車圖示傾斜並下沉,一個環向外脈動,徽章彈出。必須有東西明顯接住被拋射的物體。
- 不要剪裁狀態標記。 變形
overflow:hidden膠囊內的徽章會被切掉;將其放在裁剪範圍外的包裝器中。 - 不要重複資訊。 購物車膠囊顯示「3 pieces 195 baht」旁邊還有一個顯示 3 的徽章。現在僅顯示總額。
- 以固定比例繪製插圖。 磁磚內百分比定位的 CSS 形狀會在磁磚變寬時拉伸。使用包含 viewBox 並居中於磁磚的內聯 SVG。
無障礙與穩健性檢查清單
- 尊重減少動畫(核心已處理;確保 CSS 關鍵影格和過渡也在
prefers-reduced-motion媒體查詢中被移除)。 - 使用真正的
button、input、role="switch"、role="tab"、role="slider"等,並搭配正確的aria-*狀態,在啟動動畫的同一個呼叫中更新。 - 被變形隱藏的內容應設定為
inert(且不得攔截點擊:為不可見圖層設定pointer-events:none,然後在可見狀態時重新啟用)。 - 當焦點控制項消失於變形中時,有意識地移動焦點:移動到替代控制項,對話框關閉後移回觸發器,且僅在元素再次可見後(
visibility:hidden元素不可聚焦)。
…
[為翻譯截斷]