Directory
Posts
Categories
Tags
Table of Contents
Table of Contents
704 words
4 minutes
将 Codex 宠物搬上博客
功能概述
Inni Companion 是一只可拖动、可交互的 AI 伴侣小精灵,默认显示在页面右下角。支持 4 种动画状态:待机、挥手、等待、回顾,由 Codex 生成的像素 spritesheet 切分而来。
像素精灵图结构
精灵图由 Codex 生成,排列方式为 8列 × 9行,每行动画帧数不同:
| 动作 | 所在行 | 帧数 |
|---|---|---|
idle 待机 | 第 0 行 | 6 帧 |
waving 挥手 | 第 3 行 | 4 帧 |
waiting 等待 | 第 6 行 | 6 帧 |
review 回顾 | 第 8 行 | 6 帧 |
每帧尺寸 = 原图宽度 ÷ 8,高 = 原图高度 ÷ 9。
GIF 切分脚本
用 Python Pillow 从 spritesheet 中自动切分并导出为透明 GIF:
1from PIL import Image2
3spritesheet = Image.open('spritesheet.png')4fw = spritesheet.width // 85fh = spritesheet.height // 96
7names = ['idle', 'waving', 'waiting', 'review']8row_frames = [0, 3, 6, 8]9num_frames = [6, 4, 6, 6]10
11for name, row, nf in zip(names, row_frames, num_frames):12 frames = []13 for col in range(nf):14 frame = spritesheet.crop((col*fw, row*fh, (col+1)*fw, (row+1)*fh))15 if frame.mode != 'RGBA':16 frame = frame.convert('RGBA')17 frames.append(frame.convert('RGB'))18
19 frames[0].save(20 f'{name}.gif',21 save_all=True,22 append_images=frames[1:],23 duration=[600]*nf,24 loop=0,25 disposal=226 )导出时必须保留透明通道(RGBA 模式),否则像素角色周围会出现难看的白边。
添加到网页
1. HTML 部分
在页面底部添加一个固定定位的容器:
1<div id="inni-companion">2 <img3 id="inni-sprite"4 src="/images/inni/idle.gif"5 alt="inni"6 style="cursor: grab; width: 80px;"7 />8</div>2. CSS 样式
1.inni-companion {2 position: fixed;3 bottom: 20px;4 right: 20px;5 z-index: 9999;6 pointer-events: none;7}8
9.inni-sprite {10 width: 80px;11 height: auto;12 pointer-events: all;13 cursor: grab;14 image-rendering: pixelated;15}16
17.inni-sprite:active {18 cursor: grabbing;19}3. JavaScript 交互逻辑
1const inni = document.getElementById('inni-companion');2const sprite = document.getElementById('inni-sprite');3
4const anims = {5 idle: '/images/inni/idle.gif',6 waving: '/images/inni/waving.gif',7 waiting: '/images/inni/waiting.gif',8 review: '/images/inni/review.gif',9};10
11let isDragging = false;12let currentX = window.innerWidth - 130;13let currentY = window.innerHeight - 160;14let idleTimer;15
16// 初始化位置17inni.style.cssText = `position:fixed;left:${currentX}px;top:${currentY}px;z-index:9999;`;18
19// 拖动逻辑20sprite.addEventListener('mousedown', (e) => {21 isDragging = true;22 sprite.src = anims.waving;23 clearTimeout(idleTimer);24 e.preventDefault();25});26
27document.addEventListener('mousemove', (e) => {28 if (!isDragging) return;29 currentX = e.clientX - sprite.offsetWidth / 2;30 currentY = e.clientY - sprite.offsetHeight / 2;31 inni.style.left = currentX + 'px';32 inni.style.top = currentY + 'px';33});34
35document.addEventListener('mouseup', () => {36 if (!isDragging) return;37 isDragging = false;38 idleTimer = setTimeout(() => sprite.src = anims.idle, 3000);39});40
41// 滚动到底部 → review42window.addEventListener('scroll', () => {43 const atBottom = window.scrollY + window.innerHeight >= document.body.scrollHeight - 10;44 sprite.src = atBottom ? anims.review : anims.waiting;45});动画切换规则
| 状态 | 触发条件 | 动画 |
|---|---|---|
idle | 默认 / 停止交互 3 秒后 | idle.gif |
waving | 鼠标拖动 inni 时 | waving.gif |
waiting | 页面滚动中(非底部) | waiting.gif |
review | 滚动到页面最底部 | review.gif |
位置持久化(可选)
使用 sessionStorage 记住用户上次的位置:
1// 读取2const savedX = sessionStorage.getItem('inni-x');3const savedY = sessionStorage.getItem('inni-y');4if (savedX && savedY) {5 currentX = parseFloat(savedX);6 currentY = parseFloat(savedY);7}8
9// 保存10document.addEventListener('mouseup', () => {11 sessionStorage.setItem('inni-x', currentX);12 sessionStorage.setItem('inni-y', currentY);13});注意事项
- GIF 透明背景:导出时保留透明通道(RGBA 模式),不要加白色底
- Spritesheet 排列:精灵图必须严格按照 8×9 网格排列
- 帧尺寸:每格尺寸 = 原图宽÷8 × 高÷9
- 移动端适配:将
mousedown/mousemove/mouseup替换为touchstart/touchmove/touchend - z-index:设为 9999 以上,避免被其他元素遮挡
相关文件
- 组件代码:
src/pages/index.astro(底部 script 区块) - 样式:
src/styles/global.css中的.inni-companion、.inni-sprite - 图片目录:
public/images/inni/
将 Codex 宠物搬上博客
/posts/inni-companion-guide/ Some information may be outdated
Directory
Posts
Categories
Tags
Table of Contents