• 简体中文
  • 启动与加载

    两种等待:即将到达的记录,和时长未知的启动。

    先看效果

    左边:三个先加载再登场的头像槽。LoadingMark 在 ready 之前一直显示轮廓;下面真正的角色始终挂载着、处于暂停,所以切换是交叉淡入,而不是第一帧对着一片空白。右边:一个已经持续到该说点什么的启动。

    loadingloadingloading
    正在启动正在把一切准备就绪。
    import { useEffect, useState } from 'react';
    import { MarkStageProvider } from '@aora-bot/react';
    import { BootLoop, LoadingMark } from '@aora-bot/surfaces';
    
    function Slot({
      shape,
      color,
      delay,
    }: {
      shape: 'pebble' | 'hex' | 'cloud';
      color: 'cyan' | 'violet' | 'green';
      delay: number;
    }) {
      const [ready, setReady] = useState(false);
    
      useEffect(() => {
        const timer = setTimeout(() => setReady(true), delay);
        return () => clearTimeout(timer);
      }, [delay]);
    
      return (
        <span className="site-demo__cell">
          <LoadingMark ready={ready} shape={shape} color={color} state="spawning" sizePx={48} />
          <span className="site-demo__label">{ready ? 'ready' : 'loading'}</span>
        </span>
      );
    }
    
    export default function Demo() {
      const [cycles, setCycles] = useState(0);
    
      return (
        <MarkStageProvider>
          <div className="site-demo">
            <div className="site-demo__row">
              <Slot shape="pebble" color="cyan" delay={900} />
              <Slot shape="hex" color="violet" delay={1800} />
              <Slot shape="cloud" color="green" delay={2700} />
            </div>
            <BootLoop
              widthPx={220}
              sizePx={64}
              durationMs={1800}
              onCycle={setCycles}
              body={cycles < 3 ? undefined : '这次比平时慢一些。'}
            />
          </div>
        </MarkStageProvider>
      );
    }

    槽只拿到 state="spawning",别的什么都没给:真正的角色在 ready 之前处于暂停,所以它的状态时钟从交叉淡入那一刻开始走,gather 腰带播放,两秒后渲染器自己转到 idle。LoadingMark 不转发 onSettle;需要知道这一刻的宿主,在记录到手之后直接用 CharacterMark。BootLoop 从 effect 而不是从计时器里上报周期数,这样在回调里重渲染的宿主也不会让计时器在周期中途重启。

    什么时候用

    两种不同的等待。第一种是智能体自己的记录正在到达——几百毫秒里头像槽是空的,版面即将跳动。第二种是某个东西在启动,时长未知,进度条在这里只会撒谎。角色两种都能接:先是一个即将到来的脸的轮廓,然后是一次登场;或者是一种诚实地不知道何时结束的持续活动。

    用到的组件

    • LoadingMark——角色自己的轮廓做成闪动的遮罩,ready 时交叉淡入真正的角色。
    • BootLoop——设备边框里的不定时长等待;onCycle 让宿主逐步升级提示。
    • CharacterMark——loading、spawning、onSettle。
    • MarkStageProvider——一页占位符一起翻转时应该共用一个引擎。
    • HudPlate——启动用 sync 表盘,它表达的是一个生命周期而不是一个分数:尝试连接前是 idle,连接中是 syncing,落地时是 done 或 error。只有宿主手里有真实分数时才用 progress;按编造速率填满的环,只是绕了一圈的撒谎进度条。

    状态怎么走

    从到时机
    —loading请求已发出;whirl 让身体漂移,编号 12
    loadingspawning记录到了;gather 腰带把身体聚拢出来
    spawningidle2 秒后自行收束——STATE_SETTLE_TO.spawning
    —workingBootLoop 打开
    workingthinking每过 durationMs;一对就是一个周期
    thinkingworking……再回来;每转满一圈触发一次 onCycle(n)

    spawning 是一个瞬间,不是一种状况。宿主否则都要写同一个离开它的超时,所以由渲染器掌握时钟,并通过 onSettle(from, to) 通报这次转移。减少动态效果会跳过花活,但收束照样触发。

    每个状态的样子

    状态眼睛嘴特效编号HUD
    loading加载dot → dotLooknonespinner0536—
    spawning启动dotWide → dotdotgather08—
    working工作dotDown → dot → dotDownnonearc1632gauge
    thinking思考dotLook → dotUp → dotLookflatdots30context
    idle空闲dot → dot → dot → dotLeft → dot → dotRightnone—02—

    whirl 在每个轴上叠两条频率互不相关的正弦波,所以在任何人等待的时间内漂移都不会重复。gather 用两秒把五个粒子螺旋收进来,然后表现得像 progress。working 完全没有腰带——它只是眯眼、身体前倾——这正是 BootLoop 把它和 thinking 配对的原因:有腰带和没腰带之间的交替本身就是活动。

    接下来