深夜写代码的两个月,我悟了
入职这家上市公司刚好62天。坦白说,前两周差点想跑路——不是因为加班多(虽然确实多),而是技术债堆得比我老家楼下的快递柜还满。不过现在嘛,倒有点上头了。尤其是上周五凌晨两点,一边啃冷掉的披萨一边调一个诡异的前端动效卡顿问题,突然有种“原来如此”的通透感。
这篇文章不讲大道理,就说说我这两个月在前后端夹缝中求生存的一些实战碎片。如果你也刚进一家“历史悠久”的公司,或者正被某个看似简单实则坑爹的需求折磨,或许能会心一笑。
被产品经理“逼”出来的动画优化
事情起源于一个看起来人畜无害的需求:“首页加个微交互动效,用户滑到模块时元素优雅入场”。PM原话是“就那种很丝滑的感觉,参考苹果官网”。
我:好的,没问题。(内心OS:又是Lottie?还是Intersection Observer + CSS Animation?)
结果一查老代码,好家伙,整个首页是用 jQuery 写的 SPA,路由切换靠 display: none/block,性能监控压根没埋点。更绝的是,这个页面同时承载着双11期间80%的流量入口——换句话说,一旦卡了,老板的血压会比我的CPU温度升得还快。
第一版方案:直接上 GSAP,链式动画安排得明明白白。本地跑得飞起,自测通过,提交测试环境。
第二天:测试同学发来一段录屏——在iPhone 11上,滚动时帧率直接掉到15fps,文字都糊成马赛克了。评论区一句“这动效是致敬PPT吗?”让我当场破防。
复盘发现,问题不在动画库本身,而在于频繁触发重排重绘。老页面里一堆 inline style 和绝对定位的 div 嵌套十层,每次动画都要重新计算布局树。而且后端接口返回的数据结构极其扁平,前端得手动拼装树形结构再渲染——这部分居然放在主线程同步执行!
前后端协作:别让数据成为拖油瓶
于是拉着后端同事小王(人称“SQL永动机”)一起看。他看了一眼接口响应,沉默三秒:“你们前端能不能别啥都自己算?这明明该我们干的。”
醍醐灌顶。
我们做了三件事:
- 后端预计算:把原本前端做的树形结构组装、状态标记等逻辑挪到服务端。新增一个
/api/v2/home/optimized接口,直接返回带shouldAnimate: true字段的嵌套数据。 - 前端懒初始化:用
IntersectionObserver监听元素进入视口,仅当可见时才触发动画初始化,避免首屏加载压力。 - CSS 硬件加速兜底:给所有动画元素加上
transform: translateZ(0),强制开启 GPU 渲染。
关键代码长这样:
// 前端:只在元素可见时启动动画
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateElement(entry.target);
observer.unobserve(entry.target); // 观察一次就够了
}
});
});
document.querySelectorAll('[data-animate]').forEach(el => {
observer.observe(el);
});
function animateElement(el) {
// 使用 transform 而非 top/left,避免重排
el.style.transition = 'transform 0.6s ease-out';
el.style.transform = 'translateY(0)';
}
// 后端(Spring Boot):预标记可动画元素
public class HomeSectionDTO {
private String id;
private List<HomeItemDTO> items;
// 新增字段,前端无需再遍历判断
private boolean shouldAnimate;
}
效果立竿见影:首屏 FCP(First Contentful Paint)从 2.8s 降到 1.4s,低端机滚动帧率稳定在 55fps+。最重要的是,PM终于闭嘴了。
技术选型不是炫技,是权衡
有人可能会问:“为啥不用 React/Vue 重构整个页面?”
答案很简单:ROI(投入产出比)太低。
我们团队负责的是技术中台,核心使命是支撑业务快速迭代,而不是追求技术先进性。在资源有限的情况下,局部优化往往比推倒重来更有效。这也是我司技术文化的一个缩影——务实,甚至有点“土”,但稳。
举个反面例子:上个月有个新人同学,热血沸腾地提议用 WebAssembly 重写一个表单校验逻辑,说能提升“极致性能”。结果呢?包体积暴涨 300KB,首屏加载多了 800ms,还引入了兼容性问题。最后被架构师一句话劝退:“用户填表单又不是在玩赛车游戏,要那么快干嘛?”
开发心得:慢就是快,少即是多
这两个月踩的坑告诉我几条朴素真理:
- 前端性能瓶颈,八成在数据处理和 DOM 操作。别一上来就换框架,先 profiling。
- 后端不是数据搬运工。合理的领域逻辑下沉能极大解放前端。
- 动画不是越多越好。克制的交互相较于花哨的特效,用户体验提升更显著。
- 深夜 coding 效率高,但别 solo。我有次凌晨三点修了个 bug,第二天发现把 prod 配置写进 test 环境了……还好没造成事故,不然简历就得更新了。
顺便吐槽一下运维兄弟:为什么每次我部署完都要手动清 CDN 缓存?就不能加个自动 purge 的 webhook 吗!(运维回复:那你提个 Jira 单呗 😅)
结语:在约束中创造价值
技术探索从来不是天马行空。真正的挑战,是在一堆 legacy code、紧迫 deadline 和模糊需求中,找到那个恰到好处的解。
我不觉得用 jQuery 就 low,也不认为不上微前端就 out。能把手里的牌打好,让用户丝滑地完成下单,让测试少提几个 P0 bug,让 PM 不再半夜钉钉轰炸你——这就是现阶段最有价值的技术实践。
对了,上周五的问题最终定位到是一个第三方统计脚本疯狂调用 getComputedStyle 导致的强制同步回流。解决方案?把它移到 web worker 里异步上报。搞定那一刻,窗外天都亮了。
但奇怪的是,我没觉得累,反而有点爽。
可能这就是程序员的宿命吧:痛,并快乐着。

评论 0