作品 / UI
Shiro 的顶部导航
毛玻璃胶囊导航:鼠标经过时有一团光斑跟着走,切换选中项时图标和底部细线一起滑过去。复刻自 Innei 的博客主题 Shiro。
复刻motion-v约 2 分钟
把鼠标移到导航上来回划,注意光斑跟着指针走;再点不同的项,看图标是怎么从上一项"搬"过来的,底部那条细线也一起滑过去。切换深色模式,主题色会从青蓝换成粉色。
三处细节
光斑。 胶囊里铺一层径向渐变,圆心是鼠标在导航里的坐标,半径是导航对角线的 1/2.5,颜色是主题色 12% 的不透明度,到半径的 65% 处完全透明。鼠标移入后 0.5 秒淡入,移出后淡出。鼠标移动时只改三个 CSS 变量,不触发组件重新渲染。
图标搬家。 只有选中项显示图标。图标和底部细线各自只渲染在选中项里,用同一个 layoutId,切换时 motion-v 会把它从旧位置补间到新位置,图标的内容立刻换成新的。文字加了 layout,图标离开时左移让位,图标到来时右移腾地方。这几处动画都是弹簧,时长 0.4 秒、不回弹,是 Shiro 全局的默认设定。
胶囊。 从上到下由 70% 不透明的浅灰过渡到 90% 不透明的白,1px 描边、很淡的阴影,加 12px 的毛玻璃。底部细线 1px,两头渐隐到透明,中间是主题色 70%。字号 14px、字重 500,选中项文字变成主题色,悬停是主题色 80%。
原站上还有两样这里没做:「时光」「更多」悬停时会弹出毛玻璃的子菜单;页面往下滚约 200px 后导航逐渐淡出,滚过 600px 再往上滚,屏幕顶部会浮出一份同样的导航。
开启「减少动态效果」时,图标和细线直接跳到新位置,光斑没有淡入淡出。
提示词
复制给 Claude、ChatGPT 或编码 agent,可以复现本页的导航。和原站相比,没有子菜单和滚动时的显隐。
用 Vue 3 + motion-v 写一个胶囊形的顶部导航(也可以用 React + motion,API 对应)。点击只切换选中项。
1. 结构:nav(相对定位、圆角 9999px)里放一层光斑和一排按钮,左右各留 14px。每个按钮 padding 7px 14px,字号 14px、字重 500,不换行。
2. 胶囊外观:背景从上到下 rgba(250,250,250,0.7) → rgba(255,255,255,0.9);box-shadow 叠 1px 描边 rgba(24,24,27,0.05)、0 10px 15px -3px 和 0 4px 6px -4px 两层 rgba(39,39,42,0.05);backdrop-filter: blur(12px)。深色:背景 rgba(24,24,27,0.7) → rgba(39,39,42,0.9),描边 rgba(244,244,245,0.1)。
3. 主题色:浅色 #33a6b8,深色 #f596aa。文字默认 #27272a(深色 #e4e4e7),悬停主题色 80%,选中主题色,颜色过渡 200ms。
4. 光斑:nav 内一个 inset: -1px 的绝对定位层,背景 radial-gradient(R circle at X Y, 主题色 12% 不透明, transparent 65%)。mousemove 时 X、Y 取鼠标相对 nav 的坐标,R = hypot(nav 宽, nav 高) / 2.5,写进 CSS 变量,不要走响应式状态。默认 opacity 0,nav:hover 时 1,transition 500ms。
5. 图标:只在选中项里渲染,放在文字左边,右边距 7px,外层是带 layoutId 的 motion 元素,固定 14×14。所有项的文字包一层带 layout 的 motion 元素,图标进出时文字平滑让位。
6. 底部细线:只在选中项里渲染,绝对定位 left/right 3.5px、bottom -1px、高 1px,背景横向渐变 透明 → 主题色 70% → 透明,同样用 layoutId。
7. 动画:layout 和 layoutId 都用 spring,duration 0.4、bounce 0。外面包 LayoutGroup 并给唯一 id,页面上有多个导航时 layoutId 不会串。
8. prefers-reduced-motion:transition 改成 duration 0,光斑和文字颜色不做过渡。
容易做错的地方:图标要真的只渲染在选中项里,而不是每项都有、用透明度切换,否则没有"搬家"的效果;图标框要固定尺寸,按需加载的图标在第一帧宽度是 0,下一帧撑开时会把后面的文字推一下,切换时看起来像抖动;光斑如果用响应式状态更新,每次鼠标移动都会让整个组件重新渲染。