← 返回 DacAI Lab

实践记录

给个人主页做一套 Zack 人物动画

记录 zhuzg.com 首页四组 Zack 人物动画从 AI 原始帧筛选、移动端比例调整,到 WebP sprite、渐进加载和静态 fallback 的完整制作与性能收口过程。

个人网站Web 动画Web 性能Sprite素材工作流

最近我给个人主页上的 Zack 做了一轮人物动画。

一开始只是觉得,主页里已经有几个固定的人物场景,如果人物始终保持静态,会有一点像插图。既然 Zack 本身就是主页视觉的一部分,可以试着让几个场景各自动起来一点。

最后上线的是四组动作:

  • Hero:挥手
  • ON MY DESK:喝水
  • STILL THINKING:思考和问号
  • OFF THE SCREEN:和囡囡互动

一共 18 帧正式动画素材。

zhuzg.com 主页 Hero 场景,右侧的 Zack 正在挥手

整个过程比我最开始想的要复杂一些。真正麻烦的地方,不是“让图片按顺序播放”,而是怎么把 AI 生成的东西整理成一套真正适合网页使用的素材。

先把动作做出来

这次没有重新设计 Zack 的形象,而是沿用主页里已经确定的人物风格,在现有静态角色基础上生成不同动作。

最开始尝试的动作并不都成功。

例如走路动画就做过一轮,但逐帧看以后问题很明显。连续几帧里腿部关系不自然,动作没有形成正常的步态循环,看起来更像几张姿势相近的图,而不是一个可以连续播放的 walking cycle。

最后这组没有上线。

未采用的 Zack walking 八帧尝试,腿部关系没有形成自然循环

生成过程中还遇到过其他问题。

有些帧里会突然多出原图没有的配饰;有时候人物动作本身可以接受,但细节在不同帧之间不稳定;也有生成结果直接把几格动作放在一张图里,这种图可以拿来观察动作趋势,但不能直接作为网页动画帧。

所以这一步并不是:

生成 18 张图,然后放进网站。

而是反复生成、筛选、淘汰,再把真正可用的帧留下来。

最后确定了四组动作:

Wave

4 帧。

Hero 里的 Zack 挥两轮手,然后停下来,不做无限循环。

我不希望主页上的人物一直重复挥手,那样注意力会被持续吸走。

Working

6 帧。

电脑前保留原本的工作状态,在这个基础上增加喝水动作和 idle timing。

这一幕的重点不是“展示动画”,而是让原来的静态场景稍微有一点生命感。

Thinking

4 帧。

保留思考动作,同时继续使用原来的问号动画。

With Nannan

4 帧。

最后一个场景里 Zack 和囡囡做一次简单互动,同样不是无限循环。

四组一共 18 帧。

动起来以后,还要重新看页面比例

桌面端完成以后,我又在手机上看了一遍。

这时发现另外一个问题:动画虽然正常,但 02 / ON MY DESK 和 03 / STILL THINKING 两幕里的 Zack 在手机屏幕上明显偏小。

桌面端看起来合理的比例,到了 390px 左右的页面宽度以后,人物存在感会下降很多。

最终移动端单独调整:

  • ON MY DESK 约放大 50%
  • STILL THINKING 约放大 40%

同时重新检查人物有没有遮挡正文、超出页面或者切掉关键部位。

这部分和动画本身没有关系,但也让我意识到,人物素材上线以后不能只检查“有没有动”。

动画是否正确、人物尺寸、页面留白和移动端视觉关系,本质上是同一个最终结果。

第一版上线以后,问题马上暴露了

视觉和动作确认以后,我把这套人物系统正式上线。

功能上没有问题:

  • 挥手两轮后停止
  • 喝水正常
  • 思考和问号正常
  • 囡囡互动正常
  • 手机端尺寸也符合预期

但第一次打开网站,很明显感觉人物出来得太慢。

有时候进入页面以后要等一段时间,快速滚到后面的场景也会看到人物区域迟迟没有准备好。

我一开始也怀疑过托管和 CDN。

继续检查资源以后,很快发现问题主要在我自己。

18 张正式动画 PNG 合计 26.512 MiB

分组大致是:

动作 原始 PNG
wave 6.601 MiB
working 8.826 MiB
thinking 6.191 MiB
囡囡互动 4.894 MiB
合计 26.512 MiB

这些原图的分辨率大约在 1086×1448 或 1448×1086。

作为 AI 生成的源文件没有什么问题。

但人物在网页里的实际显示尺寸远远没有这么大。

更麻烦的是第一版的加载方式。

当某个动作需要出现时,代码会一次创建这一组所有帧,等待所有图片完成下载和 decode,然后才把整组标记成 ready。

也就是:

动作进入当前场景
→ 下载整组图片
→ 等全部解码完成
→ 才显示动画

如果网络条件一般,人物自然就会迟到。

这时问题已经很清楚了。

不是动画逻辑错了,而是我把生成素材直接当成了网页生产素材

重新处理 production asset

这一轮没有再改动作设计。

18 张原始 PNG 继续保留,作为 source asset 和后续修改时的源文件。

真正给主页使用的版本重新处理。

四组动作分别整理成四张支持透明通道的 WebP sprite:

  • wave
  • working
  • thinking
  • nannan

于是线上动画资源从:

18 张独立 PNG

变成:

4 张 production sprite

Wave 动作从四张原始 PNG 帧整理成一张 WebP production sprite

同时按照人物实际 CSS 显示尺寸重新控制分辨率,大约保留 2× 显示尺寸,足够覆盖高 DPI 屏幕,又不再把明显过剩的原始分辨率直接送到浏览器。

WebP 使用质量 92,透明通道质量 100。

处理后的体积:

动作 优化前 优化后
wave 6.601 MiB 0.518 MiB
working 8.826 MiB 0.368 MiB
thinking 6.191 MiB 0.399 MiB
囡囡互动 4.894 MiB 0.648 MiB
合计 26.512 MiB 1.933 MiB

总体积下降 92.71%

逐帧检查以后,没有看到值得为了继续省体积而接受的明显画质损失,所以没有再继续追求更低的数字。

资源变小还不够

如果只是把 PNG 换成 WebP,问题只解决了一半。

我同时重新整理了加载逻辑。

现在主页打开以后,不会一次把四组动画全部下载下来。

大致顺序变成:

页面打开
→ 当前静态 Zack 正常显示
→ 优先准备当前场景动画
→ 动画 ready 后切换
→ 后台继续准备下一个场景

顺序基本是:

wave
→ working
→ thinking
→ with-nannan

还有一个比 preload 更重要的改动:

动画没有准备好时,静态人物不能消失。

第一版里存在一种情况:动画尚未 ready,但静态人物已经被隐藏,于是用户看到的就是一个空位置。

现在静态 Zack 始终作为 fallback。

无论动画正在加载、decode 失败、资源请求失败,甚至 JavaScript 没有正常运行,页面至少应该先保留静态人物。

我的目标变成了:

网络差的时候,可以晚一点动,但不能晚一点才有人。

这比单纯追求动画尽快 ready 更重要。

实际结果

为了确认这次修改不是单纯“感觉快了”,我做了一组冷缓存、4 Mbps 带宽、100 ms 延迟的受控测试。

优化前:

  • Hero 静态人物出现:11.98 秒
  • 挥手动画 ready:27.12 秒

优化后:

  • Hero 静态人物出现:0.83 秒
  • 挥手动画 ready:2.61 秒

这组数据当然不代表所有人的真实网络环境,但前后条件一致,已经足够看出差异。

上线前后也重新检查了:

  • 桌面端
  • 390px 手机端
  • 四组完整动作
  • 挥手停止逻辑
  • 问号
  • 快速上下滚动
  • prefers-reduced-motion
  • sprite 404
  • decode failure
  • 无 JavaScript fallback

手机端之前调整过的 02 / 03 人物尺寸也保持不变。

这轮到这里就结束了。

这次真正留下来的东西

如果只看最后的实现,这件事并不复杂:

PNG 换成 WebP,减少请求,按场景预加载,保留静态 fallback。

这些都是很常见的 Web 做法。

但这次对我更有用的结论是:

AI 生成素材只是 source asset,不等于 production asset。

一张 AI 图片在屏幕上“看起来已经完成”,并不意味着这个文件就适合直接交给网站。

至少还要继续判断:

  • 尺寸是不是明显过剩
  • 文件格式是否适合网页
  • 多帧素材应该怎么组织
  • 什么时候加载
  • 加载失败以后显示什么
  • 手机端是否仍然成立
  • 动画本身是不是值得一直循环

以后如果再给 Zack 增加新动作,我应该不会再把生成出的原始 PNG 直接接进网页。

现在这套流程已经比较明确:

生成原始动作帧
→ 筛选和淘汰
→ 保留 source asset
→ 生成网页 production asset
→ 按场景渐进加载
→ 静态人物作为 fallback
→ 桌面和手机重新检查

这也是这轮主页人物动画真正完成以后,我觉得最值得留下来的部分。