打开网易新闻 查看精彩图片

Frandy 今天花了大半天,盯着一个动画时间错位的 bug。光带划过时间轴,本该精准击中每个节点闪烁,实际却像醉汉走路——偏左、偏右、时早时晚,完全没规律。

他重写了四遍逻辑。四次。

问题不在代码写得蠢,在于他一直在用两套互不相干的坐标系做同一道题。

两套坐标系,各说各话

两套坐标系,各说各话

Frandy 的时间轴组件有三层:横向卡片轨道、可拖拽折叠的卡片、以及三条脊柱轨道上跑的霓虹光带。光带移动用 viewport 空间计算——从可视区域左边缘开始的像素距离。节点位置却用 card 空间——从整个轨道最左端累加所有卡片宽度,包括已经被滚出屏幕的那些。

只有当滚动位置为 0 且所有卡片完全展开时,这两个数字才碰巧相等。

用户稍微滚动一下,或者折叠一张卡片,光带和节点的"同一位置"就变成了两个完全不同的坐标。Frandy 的前三次重写都在优化计算精度,试图用更复杂的数学让两套坐标系对齐。第四次他放弃了,直接让浏览器告诉他答案。

第四次重写:停止计算,开始测量

第四次重写:停止计算,开始测量

核心改动只有一行:用 getBoundingClientRect() 取代手动累加宽度。这个方法返回元素相对于视口的实际渲染位置,和光带使用的坐标系天然一致。

代码变成这样:每次滚动、卡片宽度变化、筛选条件切换或窗口缩放时,标记一个脏标志(dirty flag),确保每帧动画最多测量一次。光带的位置和节点的位置现在从同一个源头读取——浏览器已经算好的真实像素位置。

Frandy 在博客里说,「光带现在每次都精准击中节点。因为它读的是现实。」

这个修复的讽刺之处在于:他花了四小时试图用数学打败浏览器,最后发现浏览器早就给出了正确答案。

时间轴的其余细节

时间轴的其余细节

这个组件的交互密度不低。桌面端支持橡皮筋拖拽,带弹簧物理效果;第一张卡片 sticky 固定;双击打开详情面板;卡片角标显示当前索引;折叠状态下有个"呼吸"效果的窥视圆点提示可展开。

三条脊柱轨道上的光带也有讲究:彗星拖尾、速度变化、护送偏移、节点闪烁时带旋转和涟漪效果。未来卡片用颜色回退,动画能干净重启。

主题系统做了完整的明暗模式,四种强调色可选。localStorage 记住用户选择,覆盖管理后台的默认设置。桌面端用下拉菜单,移动端是下滑面板,滚动时和返回顶部按钮互斥显示。

UI 层面还修了芯片/标签栏边框、重新对齐返回顶部按钮、增加区块间距和导航高度、优化文字透明度。

项目状态与下一步

项目状态与下一步

Frandy 的站点尚未上线。时间轴桌面版已完成,接下来是纯设计迭代——给每个区块做移动端布局,然后是管理后台。不添加新功能,先把现有的一切打磨到位。

他给自己定了条规矩:上线前必须让所有部分"看起来对"。

这个坐标空间 bug 的教训,某种程度上也适用于产品决策。当你发现自己的推导和用户的实际体验持续偏离,问题可能不在执行层,而在你预设的框架本身。Frandy 选择相信浏览器的渲染结果,而非自己维护的抽象计算——这种"向现实妥协"的思维方式,大概是前端工程师和产品经理的共通语言。

Frandy 的博客底部有个功能:模板快捷回复,用来快速回答常见问题或存储可复用片段。他还没写这个功能的使用文档——你觉得一个花了四小时才想起 getBoundingClientRect() 的开发者,会先写文档还是先写代码?