文章目录滑不动?锅在 flex,账在我头上
PC 端文章页的目录因侧栏使用 flex 布局而被压缩导致滚动失效,修复只需调整 flex 子元素的收缩属性;随后发现手动滚动被“自动跟随”功能抢占,改为监听滚动事件并设置短暂静默期解决;后台内容列表慢主要因为每次请求前同步 git 仓库,改为后台线程定期同步(45 秒一次)并加硬超时,将接口响应时间从秒级降至毫秒级。两件事共通的经验是:症状不一定是根本问题,需排查隐藏的布局或前置操作。
这周又攒了两个问题一起修:一个是 PC 端文章页的目录滑不动,一个是后台的内容列表慢半拍。两件事八竿子打不着,但排查完发现它们的剧本出奇地像——表面症状和真正的病灶,隔了好几层。老规矩,把过程记下来,给下次的自己抄作业。
一、目录滑不动:先给滚动条洗清冤屈
现象很朴素:文章长了以后,PC 端右侧的目录显示不全,底部直接被裁掉,滚轮放上去毫无反应。移动端没事,就 PC 有。
我的第一反应是滚动条样式的问题——毕竟这个站的滚动条定制过,透明度调得很低,第一眼确实看不见。改了半天透明度,滚动条是看得见了,但目录还是滚不动。看得见摸不着,那就不是样式的问题。
回头把侧栏的布局翻出来看,才找到真凶。侧栏是个纵向 flex 容器,外面套着 max-height 和 overflow-y: auto,意图很直白:最多这么高,装不下就内部滚动。但 flex 有个不太直觉的默认行为——子元素默认 flex-shrink: 1,容器装不下的时候,所有子元素会按比例被压扁。
也就是说:目录卡片、作者卡片这些加起来比视口高的时候,flex 先做的不是触发外层滚动,而是把每个卡片均匀压扁。目录内容被裁掉一截之后,卡片内部的高度刚好没超出——没超出,自然就没得滚,滚动条委屈地认为「我没有活干」。
修复反而简单得可笑:
/* 子卡片禁止收缩,装不下就让侧栏整体滚 */#left-sidebar-sticky > *,#right-sidebar-sticky > * { flex-shrink: 0;}一行 CSS,目录恢复滑动。写这段布局的时候,我大概默认了「装不下就会滚动」,但 flex 的收缩优先级排在溢出前面。这个默认值,坑过的大概不止我一个。
二、修完压缩,又撞上「弹回去」
以为完事了,实测时冒出了更迷惑的现象:我把目录往下滚到想看的小节,松手不到一秒,它自己弹回去了。
这个就不是 CSS 了,是交互逻辑打架。目录有个「自动跟随」功能——页面滚到哪个小节,目录就把对应条目滚到可视区中间,之前写的,一直工作得挺好。问题在于它太尽职:我手动滚动目录这个动作,在它眼里和「用户翻了页面」没区别,下一帧就坚持把活动项拉回中间。我的手和它的逻辑,只能活一个。
解法是给「用户的手」加发言权:在目录上监听 wheel 和 touchmove,一旦检测到手动滚动就打个时间戳,四秒之内自动跟随闭嘴:
private userScrollingUntil = 0;private readonly markUserScrolling = () => { this.userScrollingUntil = Date.now() + 4000;};
// scrollToActiveItem 开头:if (Date.now() < this.userScrollingUntil) return;四秒是试出来的。太短的话页面稍微一滚它又开始抢方向盘,太长的话点到新小节时目录跟不上趟。
顺手还对了另外两笔账:侧栏的 top 和 max-height 之前是各写各的,底部会超出视口几十像素,这次对齐了;滚动条透明度也从「隐身模式」调回到肉眼可见——一个用户看不见的滚动条等于没有滚动条,这是我第一节白忙活半小时换来的觉悟。
三、后台慢半拍:接口在替 git 排队
第二个问题是后台的内容列表越用越慢,刷新一次要等好几秒,网络不好的时候直接超时。
排查时先看接口本身——查询逻辑没什么毛病,本地数据读取快得很。但每个读接口在干活之前都先干了一件事:同步一次内容仓库。当初的思路是「读之前先拉最新,保证内容新鲜」,听起来很负责,代价是每次请求都背一次网络往返。列表页一打开,好几个接口排着队跑网络。同步快的时候几百毫秒,慢的时候——就是你看到的转圈圈。
修复思路一句话:把慢的东西从关键路径上挪走。仓库同步不再跟着请求走,改成后台线程自己定期做,同步加硬超时,防止哪次网络抽风把线程挂死在一个请求上:
def _repo_sync_loop(interval=45): while True: try: gitops.pull() except Exception: pass # 这轮失败了下轮再来,别影响主流程 time.sleep(interval)同步周期 45 秒。对个人后台来说,「内容最多延迟一分钟」和「每次打开都等几秒」之间,根本不用犹豫。改完之后列表页的响应从秒级等待回到毫秒级,终于像个本地工具该有的样子。
写在最后
两件事修完,发现教训居然是同一句话:
症状出现在哪里,病灶不一定在哪里。看得见的问题(滚不动)可能藏在看不见的布局默认值里;等得起的耗时(内容同步)不该出现在等不起的地方(接口路径上)。
再补一条给写 CSS 的自己:flex 的默认值不会「大概按我想的来」。flex-shrink 这个属性我看了很多年,第一次真正理解它,是在自己博客的目录滚不动的那天。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












