CharacterMark
单个动画角色。库里其余一切都建立在它之上。
绘制风格
styleId 选画风,variant 选这个画风里的变体。几何、动画和脸在它们之间完全相同——只有颜料变了,所以新状态或新角色会同时出现在每一种风格里。每个画风有一页:Vector、Pixel、Kaomoji。下面是 vector 的全部变体:
solid 是默认:身体是平涂的填色块,cute 脸是白色。
outline 和 glass 把这种关系反过来。身体不再填满时,白色的脸会看不见,所以这两种变体的脸改用墨色画——同一剪影从另一面读。
描边宽度在形状空间里是常量,而不是像素,所以它随角色缩放。钉在设备像素上的描边在大角色上会细得像线,在小角色上又会把它淹没。
脸
face 在三种脸里选:默认的 cute,以及 eyes 和线条脸 line,后者用同一线宽的笔画加上鼻子和嘴。nose 只对 line 有效,从六种里挑一种;省略则由 seed 推导,传 null 则不画。
两个 prop 都可以在已挂载的角色上改变:脸在样式间缓动,鼻子交叉淡入。根元素带有 data-face 供样式使用。
什么时候用它
角色独自出现时用 CharacterMark——英雄区、空状态、页头里的单个头像。一次要好几个角色时,优先用 MarkStageProvider,它让长得一样的角色共享一份动画。
跟随指针
映射不是线性的。距离经过平方根,这样靠近角色的小动作仍然灵敏,远处也能到达极限——线性映射会让近处的运动显得迟钝。
强调
暂停
paused 取消角色对共享 ticker 的订阅,并冻结当前帧。它不会重置到中性姿态,所以滚出视口再滚回来的角色看起来像是一直在等,而不是重新开始。
命令式动作
burst() 在 prefers-reduced-motion 下会被抑制,因为从中心向外辐射的粒子是前庭刺激。
跟随宿主已知的一个点
followPointer 自己监听指针。followTarget 则接受一个视口像素坐标——另一个元素的中心,或者宿主出于自己的原因已经在跟踪的指针位置——引擎每帧用同一套平方根映射去换算。两者都设时 gazeTarget 优先。
墨色、眼睛与线宽
ink 用一个纯色或两段渐变覆盖身体的颜料,给必须匹配品牌色而不是调色板的角色用;null 交还给主题 token。eyeScale 是眼睛大小的静态倍数,叠在强调弹簧之上。lineWeight 把鼻子、嘴和 HUD 表盘一起缩放,范围是 LINE_WEIGHT_RANGE(0.8–1.25)——一个角色一支笔,于是脸和 HUD 始终是同一笔画。
HUD
hud 在身体外的环上画一个表盘;null 移除它。HUD 开着时五官会收掉——身体留成剪影,读数在环上。规格就是协议携带、HudPlate 校验的那个 HudSpec,表盘按角色的 lineWeight 描边。
规格按值比较而不是按引用,所以内联写 hud={{ kind: 'progress', value }} 不会每次渲染都重启表盘。
知道状态何时落定
有限状态——spawning、waking(见 STATE_SETTLE_TO)——走完自己的流程后,引擎会自行前进。onSettle(from, to) 在那一刻触发,宿主对状态的认知可以跟着走,而不必再维护一份重复的计时器。
相反的问题——输入变化得比角色能跟上的速度还快——由 useMarkSettle 处理。
为什么它从不重渲染
React 挂载一个空的宿主元素,然后引擎接管。prop 变化被命令式地推进控制器,而不是重渲染:
每秒六十次走 reconciler 去设十几个属性,代价远高于动画本身。挂载 effect 故意没有依赖——在 prop 变化时重建控制器会重启动画。
API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
antics | boolean | false | Let an unattended character occasionally hop or spin on its own. Default false. |
autoPause | boolean | AutoPauseOptions | false | Stop animating while hidden, scrolled out of view, or under reduced motion. Read at mount. Off by default. |
badgeColor | string | 'var(--aora-accent)' | Corner badge colour, shown in the `notifying` state. |
children | string | number | bigint | boolean | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | Promise<AwaitedReactNode> | null | — | — |
className | string | — | — |
color | "black" | "brown" | "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "magenta" | "pink" | "gray" | — | Ink colour from the palette. |
emotionId | string | — | Protocol id, for hosts that speak the wire format. Ignored when `state` is given. |
emphasis | boolean | false | Enlarge and converge the eyes, for hover states. |
entryEffects | boolean | true | Play the one-off flourish some states define on entry. Default true. |
eyeColor | string | — | Colour of the eye cut-outs. Defaults to the surrounding background token. |
eyeScale | number | — | Static multiplier on eye size, on top of the emphasis spring. |
face | "cute" | "eyes" | "line" | — | Face style: `cute` (the default), the plain `eyes`, or `line` — eyes plus a stroked nose and mouth. |
faceTune | Partial<FaceTune> | — | Multipliers on the fitted face box: overall size, eye gap, height, eye width and height. |
followPointer | boolean | false | Follow the pointer automatically. |
followTarget | { x: number; y: number; } | null | null | Eyes track this point, in viewport pixels — another element's centre, or a pointer position the host is already tracking for its own reasons. `gazeTarget` wins when both are set. |
gazeTarget | { x: number; y: number; } | null | null | Eyes track this point. Components in [-1, 1] relative to the centre. |
hud | HudSpec | null | null | A dial drawn in the ring outside the body. `null` removes it. |
ink | InkPaint | null | null | Override the body paint: a flat colour or a two-stop gradient. `null` returns it to the theme token. |
lineWeight | number | — | Multiplier on the shared line weight, 0.8–1.25. Applies to the nose, the mouth and the HUD dials together, so one character can be drawn with a heavier pen without its features disagreeing with each other. |
nose | "slant" | "angular" | "hook" | "long" | "curve" | "bow" | null | — | Nose for the line face. Defaults to one derived from the seed; `null` draws none. |
onSettle | ((from: "idle" | "sleeping" | "drowsy" | "bored" | "waking" | "powering-down" | "listening" | "thinking" | "searching" | "reading" | "writing" | "working" | "loading" | "progress" | "radar" | "orbit" | "dictating" | "uploading" | "sending" | "receiving" | "messaging" | "waiting" | "spawning" | "composing" | "dragging" | "happy" | "excited" | "laughing" | "playful" | "proud" | "celebrate" | "bouncing" | "shy" | "sad" | "angry" | "confused" | "suspicious" | "surprised" | "scared" | "curious" | "humming" | "alerting" | "notifying" | "refusing", to: "idle" | "sleeping" | "drowsy" | "bored" | "waking" | "powering-down" | "listening" | "thinking" | "searching" | "reading" | "writing" | "working" | "loading" | "progress" | "radar" | "orbit" | "dictating" | "uploading" | "sending" | "receiving" | "messaging" | "waiting" | "spawning" | "composing" | "dragging" | "happy" | "excited" | "laughing" | "playful" | "proud" | "celebrate" | "bouncing" | "shy" | "sad" | "angry" | "confused" | "suspicious" | "surprised" | "scared" | "curious" | "humming" | "alerting" | "notifying" | "refusing") => void) | — | Called when a finite state has run its course and the engine has moved on by itself. Lets a host's own idea of the state follow without keeping a duplicate timer. |
paused | boolean | false | Freeze in place. The current frame is held rather than reset. |
poseHome | Partial<PoseHome> | — | Rest pose — the slight head turn held when nothing else is happening. |
preset | "mochi" | "sprout" | "boba" | "dot" | "pip" | "byte" | "momo" | "bao" | "teal" | "tato" | "hexa" | "tri" | "gemma" | "nimbo" | "dew" | "leafy" | "bloom" | "lucky" | "shroom" | "oak" | "pokey" | "nashi" | "pomme" | "berry" | "cheri" | "twinkle" | "luv" | "ember" | "chime" | "jelly" | "beep" | "luna" | "finn" | "inky" | "miso" | "hop" | "bruno" | "chirp" | "waddle" | "oink" | "rusty" | "kiki" | "maru" | "zip" | "fuzz" | "lulu" | — | A named character from the cast. Supplies `shape`, `color`, `face` and `nose` when they are not given. |
ref | Ref<CharacterHandle> | — | — |
seed | string | — | Seed for per-instance randomness so neighbours do not blink in unison. |
shape | "mochi" | "circle" | "blob" | "pebble" | "egg" | "bun" | "tofu" | "pill" | "hex" | "onigiri" | "gem" | "cloud" | "drop" | "leaf" | "flower" | "clover" | "sprout" | "mushroom" | "acorn" | "cactus" | "pear" | "apple" | "strawberry" | "cherry" | "star" | "heart" | "flame" | "bell" | "ghost" | "bot" | "crescent" | "boba" | "fish" | "octopus" | "cat" | "bunny" | "bear" | "chick" | "penguin" | "pig" | "fox" | "bob" | "topknot" | "spiky" | "curly" | "twinbuns" | — | Body shape. |
sizeCss | string | number | — | Any CSS length, taking precedence over `sizePx`. |
sizePx | number | 28 | Rendered size in pixels. Defaults to 28. |
sourceId | string | — | Id for the root SVG group, letting `CharacterMarkMirror` reuse this instance instead of starting its own animation. |
state | "idle" | "sleeping" | "drowsy" | "bored" | "waking" | "powering-down" | "listening" | "thinking" | "searching" | "reading" | "writing" | "working" | "loading" | "progress" | "radar" | "orbit" | "dictating" | "uploading" | "sending" | "receiving" | "messaging" | "waiting" | "spawning" | "composing" | "dragging" | "happy" | "excited" | "laughing" | "playful" | "proud" | "celebrate" | "bouncing" | "shy" | "sad" | "angry" | "confused" | "suspicious" | "surprised" | "scared" | "curious" | "humming" | "alerting" | "notifying" | "refusing" | — | Animation state. |
style | CSSProperties | — | — |
styleId | StyleId | — | The style series: `vector` (default), `pixel` or `kaomoji`. Named `styleId` because `style` is already the inline CSS. Switching it at runtime replaces the renderer and keeps the character's state and motion. |
styleOptions | Record<string, unknown> | — | Parameters of the series, such as `{ grid: 32 }` for `pixel`. Clamped by the engine. |
variant | "solid" | "outline" | "glass" | "sticker" | "paper" | "soft" | "riso" | (string & Record<never, never>) | — | Paint variant within the style series, such as `outline` for `vector`, `dot` for `pixel` or `bubble` for `kaomoji`. Falls back to the surrounding `AppearanceProvider`, then to the series' own default. |
接下来
- MarkStageProvider——按外观共享引擎
- 状态——全部动画状态,分组列出
- HudPlate——表盘旁边的一个数字