全站动效记录 02:Header、首页入场和滚动揭示的顺序
首页动效的重点不是动得多,而是先后顺序。加载层退场后,header、欢迎语、书法图和后续板块才依次出现。
首访加载修好后,首页还剩一个问题:谁先出现。
如果加载层还没走完,header 已经在上面固定好了,页面会显得很急。反过来,如果每个模块都等太久,又会变成慢吞吞的展示页。
最后的顺序是:
- 首访加载层盖住页面。
- 加载层开始退出。
- 首页和 header 同时变为可见,但 header 稍早一点。
- 欢迎语、书法图、名片和按钮分组进入。
- 用户向下滚动时,后续板块再进入。
Header 不自己抢跑
Header.tsx 读取 useHomeEntranceReady()。入口还没完成时,header 是隐藏状态:
const headerHidden = { opacity: 0, y: -16, filter: "blur(4px)" };
const headerShown = { opacity: 1, y: 0, filter: "blur(0px)" };三个区域分别进入:左侧站名、中间导航、右侧控制区。它们之间有很小的延迟:
transition={headerTransition(0.05)}
transition={headerTransition(0.1)}这不是为了炫技,只是让顶部导航像落到页面上,而不是突然贴在那里。
首页第一屏是一组入场
Hero.tsx 里有几组 variants:
heroVariants负责整组 stagger。itemVariants负责欢迎语、名片、按钮。imageVariants负责书法图。actionVariants负责名片里的几个入口。
这些动画都只动 opacity、y、scale。没有动高度,也没有动布局。这样浏览器不用反复重新排版,页面会稳很多。
首页第一屏的下滑箭头也不是一直显示。它会看第二屏的位置:
homeContent.getBoundingClientRect().top >
window.innerHeight * SCROLL_CUE_CONTENT_THRESHOLD;第二屏进入视野后,箭头自动淡出。回到第一屏,它再淡入。这个小东西的作用只是提示“下面还有内容”,不应该在后面一直抢注意力。
滚动揭示必须等首页准备好
HomeReveal.tsx 用 useInView 判断元素是否进入视口:
const shouldReveal = reduceMotion || (homeEntranceReady && isInView);这里的重点是 homeEntranceReady。之前的 bug 就出在这里:首次加载后向下滚动,有些板块没有入场动效;从其它页面回来反而正常。
原因是观察和首页入口不是同一条时间线。现在 HomeReveal 必须等入口动画完成后才算 ready。这样首次进入和返回首页看到的是同一套逻辑。
后续板块保持轻
首页第二屏之后有最近更新、最近听歌、最近摄影。它们通过 HomeReveal 做进入,但内部交互仍然很轻:
- 最近更新只在 hover 时改变文字和边线。
- 最近听歌的唱片缓慢转动,长歌名只在 hover 或 focus 时滚动。
- 最近摄影的图片 hover 时轻微上移和改变透明度。
这几个动效的目标是提示“这里可点”,不是重新表演一次页面加载。
小结
首页动效最重要的是时间链。
加载层结束后,header 和第一屏入场。第一屏完成后,滚动板块再根据视口出现。这样用户看到的是一条连续的路径,不是几个组件各自开跑。

