bloub-to-vue-and-react
| Install | |
|---|---|
npm install bloub-to-vue-and-react |
|
| Latest Version: | 0.2.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Aug 17, 2026 |
| Links: | GitHub · npm |
bloub
一个用 SVG 还原 x.ai 机器人头像的项目:一个实心黑色形状在 14 个状态之间形变,两个白色形状作为眼睛、各自独立形变,背景为纯色。不依赖任何动画库。

介绍
bloub 是一个框架无关的头像引擎,为 Vue 3 和 React 18+ 提供了一等公民的适配器。整套东西由一个共享内核(src/bot/ + src/core/)驱动,因此两个框架渲染出的画面像素级一致,也永远不会互相漂移。
| Vue 3 | React 18+ | |
|---|---|---|
| 入口 | bloub-to-vue-and-react |
bloub-to-vue-and-react/react |
| 组件 | BloubBot |
BloubBot |
| Props | size shape color expression paper frozenAt cycle follow gaze ariaLabel |
相同 |
| Models | block state playing elapsed(通过 v-model) |
block state playing(受控)+ elapsed(输出) |
| 命令式 | seek rendAt |
seek rendAt(通过 ref) |
安装
pnpm add bloub-to-vue-and-react
vue 是 peer 依赖。react 和 react-dom 是可选 peer 依赖:只有使用 React 适配器时才需要安装。纯 Vue 项目永远不会把 React 拉进来,反之亦然。
# React 用户
pnpm add bloub-to-vue-and-react react react-dom
使用
Vue 3
<script setup lang="ts">
import { ref } from 'vue'
import { BloubBot } from 'bloub-to-vue-and-react'
const block = ref(0)
const state = ref('idle')
const playing = ref(false)
</script>
<template>
<BloubBot v-model:block="block" v-model:state="state" v-model:playing="playing" />
</template>
block 是播放游标——一段蒙太奇可能两次播放同一个状态,所以用索引来标识你所在的位置;state 作为输出跟随它。playing 用于开始和停止循环播放。
要渲染一帧精确画面而不启动动画循环(缩略图、状态看板),传入 frozenAt:
<BloubBot state="orbit" :size="120" :frozen-at="1.2" />
通过模板 ref 进行命令式播放:
<script setup lang="ts">
import { ref } from 'vue'
import { BloubBot } from 'bloub-to-vue-and-react'
const bot = ref<InstanceType<typeof BloubBot> | null>(null)
bot.value?.seek(2, 0.5) // 跳到第 2 块、0.5 秒处
bot.value?.rendAt(3.1) // 在绝对时间 3.1 秒处渲染蒙太奇
</script>
React 18+
import { useRef, useState } from 'react'
import { BloubBot, type BloubModel, type BloubBotHandle } from 'bloub-to-vue-and-react/react'
function Demo() {
const bot = useRef<BloubBotHandle>(null)
const [model, setModel] = useState<BloubModel>({ block: 0, state: 'idle', playing: false, elapsed: 0 })
return (
<BloubBot
ref={bot}
block={model.block}
state={model.state}
playing={model.playing}
onModelChange={setModel}
/>
)
}
block / state / playing 是受控的;onModelChange 会把内核的状态回报回来,让父组件在蒙太奇推进时保持同步。elapsed 是只读输出。
冻结单帧渲染:
<BloubBot state="orbit" size={120} frozenAt={1.2} />
命令式播放:
bot.current?.seek(2, 0.5)
bot.current?.rendAt(3.1)
包含内容
形状(8 种)
cercle、galet、squircle、capsule、triangle、hexagone、nuage、goutte。
颜色(12 种)
encre、creme、brun、rouge、orange、ambre、vert、turquoise、bleu、violet、rose、gris。
表情(31 个)
引擎内置 19 个面部表情和 12 个视线方向。其中带嘴巴的两个(stupefait、sourire)也会绘制出嘴巴。
| id | 含义 |
|---|---|
neutre |
中性(默认) |
attentif |
专注 |
surpris |
惊讶 |
excite |
兴奋 |
heureux |
开心 |
hilare |
大笑 |
colere |
愤怒 |
triste |
悲伤 |
effraye |
害怕 |
mefiant |
狐疑 |
confus |
困惑 |
curieux |
好奇 |
fier |
得意 |
timide |
害羞 |
blase |
冷漠 |
somnolent |
困倦 |
admiratif |
钦佩 |
stupefait |
目瞪口呆(带嘴巴) |
sourire |
微笑(带嘴巴) |
regardHaut / regardBas |
向上看 / 向下看 |
regardGauche / regardDroite |
向左看 / 向右看 |
regardHautGauche / regardHautDroite |
左上 / 右上 |
regardBasGauche / regardBasDroite |
左下 / 右下 |
regardArriereHaut / regardArriereBas |
向后上 / 向后下 |
regardArriereGauche / regardArriereDroite |
向后左 / 向后右 |
状态(14 个)
idle、thinking、wink、wide、alert、notify、exclaim、sleep、egg、hexagon、play、orbit、burst、comet——另加 swirl,保留用于界面过渡。
使用场景
- 聊天 / 助手头像——空闲时目光游移、眨眼,模型流式输出时进入
thinking,来新消息时notify。 - 响应式存在感——用
follow跟随指针,用gaze脚本编排入场序列,事件触发时alert/exclaim。 - 动画导出——冻结渲染器(
frozenAt、rendAt)支撑逐帧精确导出为 SVG、PNG、GIF 和 MP4。 - 产品自定义器——8 种形状 × 12 种颜色 × 31 个表情,持久化并作为
cycle蒙太奇回放。
体积与兼容负担
内核与框架无关,因此消费方为框架特定代码付出的成本只有一层薄薄的适配器。生产构建体积:
| 分块 | 原始 | gzip | 谁加载 |
|---|---|---|---|
共享内核(src/bot/ + src/core/) |
46.96 kB | 14.60 kB | 所有人 |
| Vue 适配器 | 2.80 kB | 1.21 kB | 仅 Vue |
| React 适配器 | 2.39 kB | 1.14 kB | 仅 React |
两个适配器是相互独立的入口,共享同一份内核分块:
- 一个 Vue 应用引入
bloub-to-vue-and-react,加载内核 + Vue 适配器,携带 0 字节 React 代码——产物中不含任何react、react/jsx-runtime或react-dom的引入。 - 一个 React 应用引入
bloub-to-vue-and-react/react,加载内核 + React 适配器,携带 0 字节 Vue 代码。 react/react-dom是可选 peer 依赖,因此纯 Vue 项目永远不会安装 React。
简而言之:约 47 kB 的内核才是真正的产品本体(形变、表情、动画);框架税是每个适配器约 1–1.2 kB(gzip),且没有任何跨框架冗余。
运行
pnpm install
pnpm dev
然后打开 http://localhost:5190。
pnpm test # vitest
pnpm build # vue-tsc --noEmit && vite build
pnpm build:lib # 为两个入口打包并生成类型声明
Vue 3、Vite、TypeScript、Tailwind 4。没有 ESLint 和 Prettier:vue-tsc 是唯一的关卡,所以完工前请先运行 pnpm build。
里面有什么
左侧的导航栏在三个视图之间切换。Customise 提供 8 种身体形状、12 种颜色和 31 个表情,跨访问保留。Animations 是一个小编辑器:把状态编排进时间轴、设置每个状态停留的时长、保存结果。Settings 存放语言(法语、英语或中文)和致谢。
屏幕上的任何东西都可以导出:头像可导出为 SVG、PNG 或动画 GIF,整条时间轴可导出为 GIF 或 MP4。静态格式完全不依赖任何库,视频编码器只在首次请求时才会按需加载。
有两个值得了解的 URL:
#planche:14 个状态并排冻结展示,用于快速目视检查。#etat=orbit&stop:直接打开某个状态,播放暂停。

为什么这些数字看起来很随意
它们是测量出来的,而不是拍脑袋定的。参考视频按 10 fps 切帧,每个状态都从帧上测量出来:轮廓用亚像素射线投射,眼睛用胶囊拟合,颜色和描边宽度用直接采样。
所以代码里的常量都是测量值,把它们四舍五入成更"顺眼"的数值会破坏相似度——而这正是本项目唯一追求的东西。有几个反直觉的点值得在改动之前了解:
| 你以为的样子 | 视频里实际的样子 |
|---|---|
眼睛是 // 方向倾斜 |
它们是 \\ 方向,相对竖直约倾斜 26° |
| 身体是方圆形 | 它是完美的圆形,径向偏差低于 0.7% |
| 过渡是弹簧动效 | 指数缓出;身体从不回弹过冲 |
| 彗星划过屏幕 | 圆点停在原地,是拖尾绕着它转 |
| 头像在静止时漂浮 | 并非如此,生命力来自目光游移和眨眼 |
docs/measurements.md 里有其余内容,包括如何重新生成提取出的轮廓。
架构说明
src/bot/ 和 src/core/ 与框架无关、与时钟无关:engine.sample(t) 是时间的纯函数。暂停、恢复、跳转到任意时间点、运行测试,都会产出同一帧画面——这正是冻结状态看板和无需 DOM 的测试套件能够实现的原因。Vue 和 React 适配器(BloubBot.vue 和 BloubBot.tsx)只是包住这个内核,做的唯一事情就是转发 props 并把帧推送到 DOM。
| docs/architecture.md | 引擎、径向轮廓形变、作为遮罩孔洞的眼睛 |
| docs/measurements.md | 测量了什么,以及如何重新生成 profiles.ts |
| docs/intro.md | 入场序列,以及为什么它只播放一个状态 |
| docs/interface.md | 布局、三栏场景、CSS 的坑 |
| docs/export.md | 导出为 SVG、PNG、GIF 和 MP4 |
| docs/i18n.md | 手写的翻译层 |
变更记录
CHANGELOG.md,每个版本一条记录——这也是判断你手上的副本是否包含某个修复的方法。
致谢
bloub — 动画引擎与素材(jeremy-prt/bloub,MIT)。
许可证
MIT。见 LICENSE。
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.0 | ^3.5.41 | MIT | |
| 0.1.2 | ^3.5.41 | MIT |