这个博客的首页原来是连续滚动。改成了整屏分页,一页一件事,共八屏:
首屏 / 自我介绍 01 / 自我介绍 02 / 做过的事 / 精选复盘 / 项目 / 专栏 / 联系
一屏容纳是逐档实测调出来的,不是估的。
一、吸附用谁的
第一个决定是吸附用哪套机制。
原生有 scroll-snap,CSS 几行就能写。但这里用不了——页面上的平滑滚动是 Lenis 提供的,它用 JS 驱动 scrollTop:
吸附用 Lenis 自带的 Snap 而不是 CSS scroll-snap——Lenis 是用 JS 驱动 scrollTop 的,和原生吸附会互相抢控制权、滚起来发抖。Snap 挂在同一个 Lenis 实例上没有这个问题,
onSnapComplete还能驱动指示器。
两个机制都想控制滚动位置,结果就是抖。
挂在同一个实例上之后,吸附完成后还能拿到回调,右侧的页码指示器直接跟着它更新。
二、mandatory 是错的
第一版用 type: 'mandatory'。用户反馈手感是「被抢走了」。
原因是 mandatory 在用户还在滑的时候就强行把页面拽向最近一页。滑动过程被插手,手感不是吸附,是失控。
改成 proximity:
type: 'proximity',
distanceThreshold: '30%',
duration: 1.1,
debounce: 500,
三条参数各管一件事:
proximity只在停下来且已经接近边界时才轻推一把。distanceThreshold: '30%'定义「接近」——落点离边界不到 30% 视口高才吸附,停在页面中间就让它停着。debounce: 500等惯性完全停下来再判断,不在滑行中途插手。
debounce 从 220 提到 500:
debounce 太小会在惯性还没停时就抢着吸附,手感发涩。
实测结果:
滑动全程 0 反向帧(原来会被拽回)
停在离边界 306px(超过 30% 阈值)时保持不动
停在边界附近才对齐整页
三、一屏装不装得下
分页的前提是内容能装进一屏。这一条逐档量过。
首屏原高 921px:
- 1440×900 超出 89px
- 1366×768 超出 201px
精选复盘 788px,在 1366×768 超出 88px。
两个方向可选:砍内容,或者按档收紧间距。选了后者,分三档:
min-width: 1024px and max-height: 820px 深压
min-width: 1024px and max-height: 900px 只压首屏
min-width: 1024px and min-height: 1000px 放大间距吃大屏留白
中间那档的调整方式值得记一笔。为了挤出 27px,第一版直接砍了字号:
font-size: clamp(46px, 9.2vw, 132px) → clamp(30px, 5.4vw, 62px)
砍掉一半多。首屏标题是整页的主体,砍字号会让整屏塌下来。改成全部从内边距要:
@media (min-width: 1024px) and (max-height: 900px) {
.page-hero :deep(.hero) { padding-top: 20px; padding-bottom: 24px; }
.page-hero :deep(.hero-foot) { margin-top: 26px; }
}
只有 700px 可用高度那一档才让一档字号。三档实测:
1440×900 132px
1920×1080 132px
1366×768 92.9px
四、检测判据写错过一次
判断页面有没有溢出,第一版用的是:
scrollHeight > clientHeight
这个判据永远为假。页面用的是 min-height,内容超了页面会自己长高——scrollHeight 和 clientHeight 一起变大,比值不变。
改成直接比可用高度:
// 检测判据从 scrollHeight > clientHeight 改为直接比可用高度——
// 页面用的是 min-height,内容超了页面会自己长高,前者永远比不出溢出
这个错误值得单独说:判据本身写错了,所以「三档零溢出」这个结论在修正之前是不成立的。
修正之后三档均为 8/8 页零溢出。
五、不分页的三种情况
窄屏(< 1024px)
矮视口(< 620px)
减弱动效(prefers-reduced-motion)
三种都退回连续滚动。
前两种有实测依据:
实测 390px 下自我介绍 1448px、精选复盘 1114px,强行一屏只会截断内容
第三种是硬要求。整屏吸附会强制改变滚动位置,这和「尊重减弱动效偏好」直接冲突。CSS 里也要同步禁用:
@media (prefers-reduced-motion: reduce) {
.page { min-height: 0; }
.pager { display: none; }
}
六、分页之后,每页只剩半屏内容
八屏分配完之后,出现一个反向问题:每页内容太少。
实测 1440×900 下多数页只填了 47%~63%,「做过的事」那页 63% 是空的。
既然分了页,每页就得撑得住。逐页补实:
做过的事:加页首概览条(项目数/时间跨度/人手),每项补详细说明、关键指标与技术标签。308px → 548px
项目:3 张卡补到 6 张,两行三列。362px → 511px
联系:补引言、about 与 timeline 两个入口、站点信息带。452px → 791px
专栏:每张卡补该栏最新一篇标题。512px → 597px
自我介绍 01:四张能力卡各补首组的真实条目。504px → 596px
自我介绍 02:补技术栈横带。522px → 603px
填充率从 47%~63% 提到 61%~100%。
同一轮里把两个区块的入场动画也补了常驻循环,因为:
实测入场动画本来就在跑(轴线 scaleX 0→0.99、圆点带回弹、卡片 clip-path 从 78% 揭到 4%、指标读数往上跳),问题是太隐蔽:一次性、1 秒内结束、触发点又在区块刚露头时,等正眼看过去通常已经播完。
触发点 top 88% → 95%,轴线 0.9s → 1.25s,圆点与节点错峰 0.11 → 0.16
七、断点从 900 降到 820
第一版把「矮视口」定在 900px 高。1440×900 的屏幕因此被当成矮视口,白白砍掉了列表摘要——精选复盘从 788px 缩到 389px。
而 1440×900 明明有空间。断点降到 820:
820 以下 深压
900 以下 只压首屏
1000 以上 放大间距吃掉大屏留白
八、两个 CSS 优先级问题
首屏被挤成一个框。
.page { padding: 24px 0; }
.page-hero { padding: 0; }
.page-hero { padding: 0 } 写在媒体查询之前,被后面同优先级的 .page { padding: ... } 覆盖了。首屏于是四边都留出内边距,看起来像一个框。
修法是在每一档里重新声明一次:
/* 首屏必须满幅出血。这行不能省——上面那条 .page 同优先级且更靠后,
会把顶部声明的 .page-hero { padding: 0 } 覆盖掉。 */
.page-hero { padding: 0; }
标题被大屏规则压小。
大屏档里有一条:
.page :deep(.big) { font-size: clamp(34px, 3.4vw, 56px); }
首屏主标题的类名也是 .big。1920×1080 下它从 132px 被压到 56px,首屏整个塌掉。
改成排除首屏:
/* 必须排除首屏——hero 里的 .big 是那个巨大的主标题,
被这条命中会从 132px 压到 56px,整个首屏塌掉 */
.page:not(.page-hero) :deep(.big) { font-size: clamp(34px, 3.4vw, 56px); }
两个问题的成因相同:同一个类名在两种语义下被复用。.page 既是容器也是列表页的内边距来源,.big 既是区块小标题也是首屏主标题。分开之后就没这个问题。
修完实测:hero 左 0、底 1、右 10(右侧那 10px 是站点滚动条本身的宽度),标题 132px 满值,8/8 页仍零溢出。
九、加载页期间的一个时序漏洞
这个站有一个启动加载页,首次访问时盖几秒。加载页盖着的时候,入场动画被暂停了——否则动画会在遮挡期间自己跑完。
副作用是页面切换的幕布动画也被冻住:
实测(加载页 3s 时点击):t=745 路由已切到 /columns 而幕布 display:none,t=1406 幕布才出现并扫过。
路由在没有幕布的情况下切换了,等加载页结束、时间线恢复之后,被冻住的补间才补播。
结论是补一行守卫:加载页盖着时直接放行,不放幕布。理由是:
真实用户点不到——加载页是全屏遮罩。但浏览器前进/后退、程序化跳转仍可能在窗口内触发导航。
十、整屏分页的账
这套改动最后收敛成一张数字表:
| 项目 | 数值 |
|---|---|
| 页数 | 8 |
| 吸附类型 | proximity,阈值 30%,防抖 500ms |
| 吸附时长 | 1.1s |
| 分页生效条件 | 宽 ≥ 1024px 且 高 ≥ 620px 且 未开启减弱动效 |
| 溢出 | 三档均 0/8 |
| 填充率 | 61%~100%(改前 47%~63%) |
| 首屏标题 | 132px(1440×900、1920×1080)、92.9px(1366×768) |
每一条都有一次实测在后面。这类改动没别的办法——「一屏装不装得下」这件事只能量,量完才知道该压间距还是该砍内容。
中间还有两处是判断上的错,不是量的问题:一是用 mandatory 吸附(把用户的滑动抢走了),二是把「矮视口」定在 900(把有空间的屏幕当成没空间)。前者的修法是换模式,后者的修法是挪断点。
■