技术探索与实践的一些思考

杰出之战士
2025-12-16 19:11
阅读 2223

去年九月,我从某一线大厂裸辞了。没错,就是那种“凌晨三点还在改需求”、“PRD文档比小说还长”的互联网公司。离职前的最后一个月,我一边在双11压测群里疯狂刷屏“后端扛不住了”,一边偷偷投简历、刷 LeetCode、调 CSS 动画帧——表面稳如老狗,内心慌得一批。

Gap 的这半年,一开始是彻底躺平:打游戏、看剧、学吉他(结果吉他现在成了衣架)。但很快发现,不写代码手会生,脑子也会锈。于是重新拾起技术博客的老习惯,把之前工作中踩过的坑、学过的新东西、面试被问懵的问题都记录下来。今天这篇,就想聊聊最近在“技术探索与实践”上的一些真实体会——尤其是当我重新开始找工作、翻出尘封半年的简历、面对各种面试题挑战时,才真正意识到:技术不是堆砌,而是解决问题的能力。


一场由“简历优化”引发的技术回溯

事情要从上周说起。我终于鼓起勇气更新了简历,准备重新杀回职场。打开 Notion 里的旧简历模板,第一反应是:“这写的啥?怎么全是‘参与XX项目’‘负责XX模块’?”——典型的“外包式描述”,毫无技术深度,更别说亮点了。

我盯着“前端动画与交互动效”这一行发呆。这是我在上家公司最感兴趣的领域,也做过几个高光项目,比如首页那个丝滑到产品经理看了想给我加薪的 Lottie 动画加载页。但简历里只轻飘飘写了句“使用 Lottie 实现加载动画”。HR 看了估计以为我会点播放按钮就完事了。

于是我决定:重做这个动画案例,用最新技术栈重构一遍,顺便写篇深度总结。一来能充实简历,二来也能检验自己这半年有没有真的“废掉”。

目标很明确:实现一个高性能、可复用、支持 SSR 的交互动画组件库,覆盖 hover、scroll、click 等常见触发场景,并且在低端机上也不卡成 PPT。


别被“炫技”带偏了方向

刚上手时,我差点掉进“技术选型陷阱”。看到 Framer Motion 很火,Three.js 能做 3D,GSAP 性能强……恨不得全塞进去。但冷静下来一想:业务场景才是爹

在上家公司,我们有个商品详情页的“悬浮按钮随滚动渐显”需求。当时为了赶双11上线,直接用了 window.onscroll + requestAnimationFrame 手搓,结果低端安卓机一滚动就掉帧,用户反馈“像在玩幻灯片”。

后来复盘发现,问题不在逻辑,而在过度监听 + 无节流。每次滚动都触发几十次回调,还频繁读取 scrollTop 这种会强制重排的属性。当时的我,满脑子想着“怎么让按钮动起来”,却忘了“怎么让它高效地动”。

这次重构,我先问自己三个问题:

  1. 用户在哪触发?(移动端 scroll vs PC hover)
  2. 性能瓶颈在哪?(布局抖动?GPU 占用?)
  3. 是否需要服务端渲染?(SEO 友好性)

带着这些问题,我才开始选型。


技术选型:平衡、妥协与现实主义

最终我选择了 Intersection Observer + CSS Transform + Web Animations API 的组合拳。为什么?

  • Intersection Observer:监听元素进入视口,天然节流,不阻塞主线程,完美替代 scroll 事件。
  • CSS Transform:利用 GPU 加速,避免 layout thrashing。
  • Web Animations API:原生 JS 控制 CSS 动画,比 setTimeout 精准,比 CSS keyframes 更灵活,还能取消、暂停、反转。

📌 小插曲:本来想上 Framer Motion,但它在 SSR 下需要额外配置,而且 bundle size 比较大。考虑到简历里要体现“性能意识”,果断放弃“开箱即用”的诱惑,选择更底层可控的方案。

下面是我封装的核心 hook:

// useScrollReveal.ts
import { useEffect, useRef } from 'react';

const useScrollReveal = (
  targetRef: React.RefObject<HTMLElement>,
  options: IntersectionObserverInit = {}
) => {
  const observerRef = useRef<IntersectionObserver | null>(null);

  useEffect(() => {
    if (!targetRef.current) return;

    const defaultOptions: IntersectionObserverInit = {
      threshold: 0.1,
      rootMargin: '0px 0px -50px 0px', // 提前50px触发
      ...options,
    };

    const handleIntersect: IntersectionObserverCallback = (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          // 使用 Web Animations API 触发动画
          entry.target.animate(
            [
              { opacity: 0, transform: 'translateY(20px)' },
              { opacity: 1, transform: 'translateY(0)' }
            ],
            {
              duration: 600,
              easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
              fill: 'forwards'
            }
          );
          observerRef.current?.unobserve(entry.target); // 触发一次后停止监听
        }
      });
    };

    observerRef.current = new Intersection Observer(handleIntersect, defaultOptions);
    observerRef.current.observe(targetRef.current);

    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect();
      }
    };
  }, [targetRef, options]);

  return {};
};

用法也很简单:

const MyComponent = () => {
  const ref = useRef(null);
  useScrollReveal(ref);

  return <div ref={ref} className="fade-in-item">Hello, I'm animated!</div>;
};

面试题挑战:你以为的“简单”,其实是深水区

写完这个 hook,我突然想到:这不就是面试常考的“懒加载/滚动动画”题吗?以前被问到时,我只会说“用 Intersection Observer”,但真让我现场写,可能连 thresholdrootMargin 的区别都说不清。

于是我把这个小功能当成一道模拟面试题挑战,逼自己回答:

“如果用户快速滚动,动画还没播完就离开视口,怎么办?”

最初版本没处理这个问题,导致元素“闪现”后消失。后来加上了 fill: 'forwards' 保证状态保留,但如果用户反复进出呢?要不要重置动画?

我又加了个 shouldReset 选项:

if (entry.isIntersecting) {
  // 播放入场动画
} else if (options.shouldReset) {
  // 重置状态
  entry.target.animate(
    [{ opacity: 1, transform: 'translateY(0)' }, { opacity: 0, transform: 'translateY(20px)' }],
    { duration: 0 }
  );
}

你看,一个看似简单的交互,背后全是细节。而这些细节,恰恰是面试官想考察的“工程思维”——你是不是只停留在 API 调用层面,还是真的理解浏览器渲染机制?


性能不是玄学,是可测量的科学

重构完,我用 Lighthouse 跑了分,结果让我有点脸红:初始版本在 Moto G4(低端安卓)上 FPS 只有 38。虽然肉眼勉强能看,但离“丝滑”差远了。

我打开了 Chrome DevTools 的 Performance 面板,录了一段滚动过程。果然,红色警告框弹出来:“Forced reflow detected”。

罪魁祸首是我之前为了“动态计算偏移量”写的一行代码:

const offset = element.offsetTop; // ⚠️ 强制同步 layout!

改成纯 CSS 的 transform: translateY(var(--offset)) 并通过 CSS 变量传递值后,FPS 直接飙到 58+。

我还做了个对比表格,记录不同方案在三种设备上的表现:

方案 iPhone 14 Pro MacBook Pro Moto G4
原始 scroll + rAF 60 FPS 60 FPS 38 FPS
Intersection Observer + CSS Transform 60 FPS 60 FPS 58 FPS
Framer Motion (默认配置) 60 FPS 60 FPS 52 FPS

💡 经验教训:不要相信“理论上高效”,一定要实测。尤其当你简历里写着“注重性能优化”时,拿不出数据就是耍流氓。


简历不是流水账,是技术叙事

现在,我把这个案例重新写进了简历,不再是干巴巴的一句话,而是这样:

高性能交互动画系统(个人项目)

  • 基于 Intersection Observer + Web Animations API 自研轻量级动画 Hook,支持 SSR 与 CSR
  • 解决低端机滚动卡顿问题,Moto G4 上 FPS 从 38 提升至 58+
  • 支持自定义触发阈值、动画曲线、状态重置,已抽象为可复用组件库
  • 技术栈:TypeScript, React, CSS Houdini (实验性)

是不是瞬间有内味了?简历的本质,是讲好你的技术故事。你用了什么技术不重要,重要的是:你解决了什么问题?带来了什么价值?有没有深入思考?


写在最后:技术探索,是为了更好地“交付”

很多人觉得“技术探索”就是学新框架、追新语法。但在我裸辞这半年里,越来越明白:真正的技术深度,体现在对“交付质量”的执着

在大厂那会儿,经常被产品经理催:“这个动效明天上线,不然双11 GMV 要受影响!” 我们前端组一度成了“特效组”,但没人关心动画会不会卡、会不会内存泄漏。直到有一次线上事故:一个无限循环的 CSS 动画导致 Safari 崩溃,客诉暴增,CTO 亲自下场开会。

那次之后,团队立了规矩:所有动画必须通过性能评审。而我也开始养成习惯:写任何交互前,先问“它值得存在吗?性能成本是多少?”

现在回头看,那段经历反而成了我技术成长的转折点。技术不是炫技,而是在约束条件下找到最优解——时间、人力、设备、用户体验,都是约束。

所以,如果你也在准备简历、应对面试题挑战,别只背八股文。试着把你做过的每一个小功能,都当成一次“微型技术探索”:为什么这么做?有没有更好的方式?数据怎么说?

毕竟,能讲清楚一个 Button 的点击反馈优化的人,比只会说“精通 React/Vue”的人,更容易拿到 Offer


PS:这篇文章写完,我的新简历已经投出去了。远程岗优先,求推荐~(顺便,我家猫刚刚在我键盘上睡着了,导致不小心提交了两次 Git commit,运维大哥别骂我 😅)

评论 0

最热最新
暂无评论
杰出之战士Lv.1
0
影响力
0
文章
0
粉丝