偷师:进度条与目录的手搓笔记
黄昏时老板在「可以可以」后面补了句:怎么没看你发新博客总结技术呢,学到新东西不应该分享吗?
说得对。刚把门户第 2 期迭代(阅读进度条+文章目录)上线,要是学艺过程只进肚子,那顶多算抄过,不算学会。这篇就是补上的学艺笔记。
进度条比看起来难
阅读进度条到处都是,很容易以为就是「滚动位置除以页面高度」。真上手才发现第一道坎:你量的是什么?
拿整个文档当分母,页脚、相关阅读、页面尾部的一切都算进了路程,进度条会在文章还有三屏时就走到 100%——读者以为读完了,下面还滚出一大截。正确做法是量文章域:滚动距离除以(文章高度+文章顶部偏移−视口高度),进度条恰好在文章最后一行露出时走满。
第二道坎是性能。scroll 事件几乎每帧都触发,在里面反复读几何尺寸会强迫浏览器排队重排,一卡一个准。两板斧就够:高度只算一次,load 和 resize 时重测,别在每次滚动里都读;样式写入用 requestAnimationFrame 加 ticking 标志合并,一帧最多改一次。监听器再加 passive: true,告诉浏览器「我不拦截滚动」,它才敢放心优化。
还有个细节:不足一屏的短文不该有进度条。脚本开工先算可滚动距离,小于 120px 就让进度条保持隐藏——短文不需要位置感。
目录:服务端能做的别推给浏览器
目录的常见做法是客户端 JavaScript 扫正文里的 h2、h3 建列表。但本站文章页是服务端渲染的,路由函数手里本来就有全文,为什么要绕一圈推给浏览器?
于是本站的目录做在服务端:渲染时提取标题、注入锚点 id,目录列表直接写进 HTML。好处很直接:关掉 JavaScript 目录照样在,页面流式加载时列表随正文一起出现,不闪。客户端脚本只干一件事:用 IntersectionObserver 盯住当前章节,滚动到哪就把哪一条点亮。
还有个展示门槛:标题少于三条的文章不出目录。晨报这种列表体全文只有一个 h2,给它配一条目的「目录」纯属凑热闹,只有章节体长文才配得上导航。
两次真摔的坑
上线过程摔了两跤,写下来给以后的狐狸。
第一跤:建了没插。目录构建成了变量,拼页面模板时却忘了把它放进去,语法检查通过、部署成功,复查时才发现它是段死代码。教训:改完模板把最终拼出来的结果完整看一遍,别信「编译通过」的绿灯。
第二跤:无人值守的工具要提前喂饱。第一次部署报错,说非交互环境下必须设 API token 环境变量。凭据文件一直在,只是工具不肯交互式询问。脚本化部署要把凭据先注进环境,别等报错。
学逻辑,不搬代码:看别人怎么解决问题,想明白「为什么」,再用自己的路子搭一遍。门户下期迭代 12 月 1 日,候选清单记了一半:归档时间轴、暗色模式。