FusionSurface 融合表面
边上长出凸起、凸起可拉断成水滴的圆角表面
安装
pnpm add @talex-touch/tuffex
import { TxFusionSurface } from '@talex-touch/tuffex/fusion-surface'
import '@talex-touch/tuffex/fusion-surface/style.css'
import '@talex-touch/tuffex/base.css' // 设计令牌与重置样式,全应用引入一次
用法
工具栏托盘
id 不变时改 center,凸起滑到新位置;从 buds 移除,凸起原地收回。
示例加载中...
分裂成水滴
detach 越过断裂点后颈部断开,凸起变成水滴,残留缩回边里。
示例加载中...
四条边
edge 选择长出的边;贴近拐角的凸起会被夹紧到直边上。
示例加载中...
最佳实践
- 同一条边上的凸起不要重叠:每个凸起占
width加两侧凹角。 - 移动凸起时保持
id不变;换id会收回旧凸起、另长一个新的。 - 重播分裂时先关闭凸起(
open: false或移出buds),或换一个新id;调小detach只会把水滴移回来。 - 根节点不设
background、border、box-shadow,也不裁剪(overflow: hidden、clip-path、contain: paint);凸起不占布局空间,父级要为它留出 padding。 - 颜色用
var(--tx-…)token 以跟随主题;阴影交给shadow,不写在子元素上。
API 参考
属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
buds | FusionSurfaceBud[] | [] | 要长出的凸起,字段见「类型」;移除的凸起先收回再卸载内容。 |
radius | number | 16 | 主体圆角,不超过短边一半;也是凸起外角的默认值。 |
fillet | number | 12 | 凸起与主体相接处凹角的最大半径,不超过凸起当前高度的一半。 |
breakAt | number | 28 | 颈部完全闭合时的 detach;约在其 96% 处断开。 |
fill | string | var(--tx-bg-color-overlay) | 表面填充色。 |
stroke | string | none | 描边颜色,沿每个凸起、颈部和水滴走。 |
strokeWidth | number | 1 | 描边宽度(px)。 |
shadow | string | var(--tx-elevation-3) | box-shadow 语法,画成剪影上的 drop-shadow();'none' 去掉阴影。 |
transition | 'snappy' | 'smooth' | 'bouncy' | SpringConfig | 'smooth' | 凸起运动的弹簧;没有时长写法,换目标时保留速度。 |
contentBlur | number | 6 | 凸起内容展开时的起始模糊(px);0 只保留淡入。 |
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
break | (id: string) | 凸起颈部断开的那一帧触发;每次分裂只触发一次。 |
settle | () | 所有弹簧静止、帧循环休眠时触发;无需运动的变化不触发。 |
插槽
| 插槽名 | Props | 说明 |
|---|---|---|
default | - | 主体内容,可交互,画在剪影之上。 |
bud | { bud: FusionSurfaceBud } | 单个凸起的内容层(带 data-bud),随凸起或水滴移动;关闭时为 inert。 |
类型
FusionSurfaceBud,即 buds 的一项:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | string | - | 跨更新的身份标识,必填。 |
edge | 'top' | 'right' | 'bottom' | 'left' | 'top' | 长出的边;改边时在新边上重新长出,不绕过拐角。 |
open | boolean | number | true | 展开或关闭,或 0..1 的展开比例。 |
center | number | 边的中点 | 沿边位置(px),上 / 下边从左量,左 / 右边从上量;会被夹紧。 |
width | number | - | 沿边尺寸(px),必填;超出直边时收窄。 |
height | number | - | 完全展开时向外的尺寸(px),必填。 |
radius | number | 表面的 radius | 凸起外角半径,受自身尺寸限制。 |
detach | number | 0 | 拉离主体的距离(px);越过断裂点后变成水滴。 |
drift | number | 0 | 被拉出部分的横向偏移(px),上 / 下边向右、左 / 右边向下为正。 |
import type {
FusionSurfaceBud,
FusionSurfaceEdge, // 'top' | 'right' | 'bottom' | 'left'
FusionSurfaceEmits,
FusionSurfaceProps,
FusionSurfaceTransition, // 'snappy' | 'smooth' | 'bouncy' | SpringConfig
TxFusionSurfaceInstance,
// fusionSurfacePath() 用到的类型,见「几何函数」
FusionSurfaceBudShape,
FusionSurfaceGeometry,
FusionSurfaceGeometryInput,
FusionSurfaceRect,
FusionSurfaceSpan,
FusionSurfaceSplit,
} from '@talex-touch/tuffex/fusion-surface'
CSS 变量
| 变量 | 说明 |
|---|---|
--tx-fusion-surface-fill | 剪影填充,由 fill 写入。 |
--tx-fusion-surface-stroke | 描边颜色,由 stroke 写入。 |
--tx-fusion-surface-stroke-width | 描边宽度,由 strokeWidth 写入。 |
--tx-fusion-surface-filter | 剪影的 filter,由 shadow 写入。 |
--tx-fusion-surface-progress | 每帧写在凸起内容层上的展开程度(0..1),供 bud 插槽读取。 |
前四个只在传入对应属性时写入,未传时可由祖先元素或主题设置。
几何函数
fusionSurfacePath() 是组件的几何部分,纯函数、不依赖 Vue 与 DOM;宿主不能改成 TxFusionSurface 时,用它在宿主上方画凸起、颈部和水滴。
import type { FusionSurfaceSplit } from '@talex-touch/tuffex/fusion-surface'
import {
FUSION_SURFACE_BREAK_PINCH,
fusionSurfacePath,
fusionSurfacePinch,
springSteps,
} from '@talex-touch/tuffex/fusion-surface'
const overlayPath = document.querySelector<SVGPathElement>('#composer-overlay path')!
let detach = 0
let velocity = 0
let split: FusionSurfaceSplit | null = null
function frame(dt: number) {
// 与组件相同的弹簧,换目标时保留速度。
;[detach, velocity] = springSteps(detach, velocity, 44, 'smooth', dt)
const pinch = fusionSurfacePinch(detach, 28)
// 像组件一样锁存断裂(组件还会把 detach 限制在断裂点)。
if (!split && pinch >= FUSION_SURFACE_BREAK_PINCH)
split = { center: 208, width: 168, height: 36, detach, drift: 0, remnant: 1, tail: 1 }
// …之后用 'snappy' 弹簧把 split.remnant 和 split.tail 降到 0。
const { d } = fusionSurfacePath({
width: 320,
height: 48,
radius: 16,
includeBody: false, // 主体由宿主自己绘制
buds: [{ id: 'message', center: 208, width: 168, height: 36, detach, pinch, split }],
})
overlayPath.setAttribute('d', d)
}
includeBody: false 的路径在宿主内闭合;要描边时先裁掉宿主内的部分,并沿 spans 断开宿主自己的边框。
输入
FusionSurfaceGeometryInput:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width / height | number | - | 主体尺寸(px),两者都为正才绘制。 |
radius | number | 16 | 主体圆角,不超过短边一半。 |
buds | FusionSurfaceBudShape[] | [] | 当前这一帧的凸起。 |
includeBody | boolean | true | 为 false 时只输出凸起、颈部和水滴。 |
baseOverlap | number | 2 | includeBody: false 时相连形状伸进主体的距离(px),用来盖住宿主边框。 |
FusionSurfaceBudShape 的其余字段与 FusionSurfaceBud 相同,但描述当前帧而不是目标:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
height | number | - | 当前向外的高度,从 0 增长即展开;小于 0.5 px 不绘制。分裂后是水滴在 split.scale 之前的高度。 |
fillet | number | 12 | 凹角的最大半径。 |
pinch | number | 0 | 颈部收窄程度(0..1);为 1 时收成一点。 |
split | FusionSurfaceSplit | null | null | 颈部断开后设置;此后忽略 pinch。 |
FusionSurfaceSplit:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
center / width / height / detach / drift | number | - | 断开那一帧的凸起;之后移动凸起只移动水滴。 |
remnant | number | - | 残留缩回主体边的进度,从 1 降到 0。 |
tail | number | - | 水滴尖尾收圆的进度,从 1 降到 0。 |
scale | number | 1 | 水滴绕自身中心的缩放;关闭分裂的凸起时组件把它降到 0。 |
输出
FusionSurfaceGeometry:
| 字段 | 类型 | 说明 |
|---|---|---|
d | string | 顺时针闭合子路径(保留 2 位小数):先是并入相连凸起的主体,再是每滴水滴;不会出现 NaN 或 Infinity。 |
spans | FusionSurfaceSpan[] | { id, edge, from, to }:相连凸起(或残留)与主体边的接合区间,宿主边框在此断开。 |
rects | FusionSurfaceRect[] | { id, edge, x, y, width, height }:凸起(分裂后为水滴)当前的外框,组件据此摆放 bud 内容。 |
辅助导出
fusionSurfacePinch(detach, breakAt):组件的颈部曲线,breakAt的前四分之一为 0,之后缓入到 1。FUSION_SURFACE_BREAK_PINCH:0.985,组件锁存分裂时的收窄程度。springSteps(position, velocity, target, config, dt):组件的弹簧积分器;dt以秒计,返回[position, velocity]。
概述
- 剪影是根节点内
z-index: -1的 SVG,pointer-events: none且aria-hidden,位于插槽内容之下。 - 加入
buds的凸起原地长出;移除的凸起先收回,期间内容保持挂载并为inert。 open、center、width、height、detach、drift各是带速度的弹簧,换目标时转向而不重来。- 分裂会锁存:调小
detach不会重新连上;关闭分裂的凸起时,水滴连同内容绕自身中心缩小。 shadow跳过inset和带扩展半径的层;var()层原样透传,只能含一层(如--tx-elevation-*)。- 减少动态效果时(实时跟随),弹簧一帧到位,越过断裂点仍触发
break。
技术实现
- 每个表面一个
requestAnimationFrame循环:逐帧积分共享弹簧(liquid/src/spring.ts),重算一条锐利的 SVG 路径,并直接写入凸起内容层的样式;静止后休眠。 - 相连凸起参照 uiarc.dev 的 Dock,只借鉴观察到的手法,未使用其代码;颈部是本库自己的轮廓模型。
- 源码:
packages/tuffex/packages/components/src/fusion-surface/。
查看源码
packages/tuffex/packages/components/src/fusion-surface/index.ts
使用场景
- 工具栏或 Dock:工具从栏上长出选项托盘、子菜单或提示。
- 面板从边上长出气泡或徽标,再让它脱离。
- 发送消息:输入框把消息拉断,飘进会话;宿主自绘时用
fusionSurfacePath()。
相关组件
| 组件 | 用途 |
|---|---|
| Fusion 交融 | 两个插槽经 goo 滤镜交融 |
| Liquid 液态流体 | 自由移动的块像水滴般融合 |
| BorderBeam 流光边框 | 沿边框游走或呼吸的光束 |