用Framer Motion打造丝滑动画效果的实战经验分享

Prog.国玲 移动 阅读 2,830
赞 13 收藏
二维码
手机扫码查看
反馈

又踩坑了,Framer Motion在移动端的动画卡顿问题

最近在用Framer Motion做移动端页面的时候,遇到了一个特别头疼的问题。页面滚动时动画会出现明显的卡顿,尤其是在低端安卓机上,简直没法看。这里我踩了个坑,折腾了好几天才搞定。

用Framer Motion打造丝滑动画效果的实战经验分享

一开始我以为是CSS样式写得不够优化,各种精简重绘区域、减少层级嵌套都试过了,结果还是卡。后来试了下发现原来是Framer Motion的默认配置和移动端有些冲突,尤其是touch事件这块。

三种方案对比,我选了最简单的

说下我尝试的几种解决方案:

  • 第一种是完全放弃Framer Motion,改用手写的requestAnimationFrame。确实流畅了,但工作量太大,所有动画都要重写。
  • 第二种是用transform替代top/left定位,这个倒是解决了部分性能问题,但复杂动画还是不行。
  • 最后发现其实是Framer Motion的layout特性导致的重绘问题,调整几个参数就搞定了。

最终方案简单粗暴,核心代码就这几行:

import { motion } from "framer-motion";

const itemVariants = {
  hidden: { opacity: 0, y: 50 },
  visible: { opacity: 1, y: 0, transition: { duration: 0.3 } }
};

function AnimatedList() {
  return (
    <motion.ul layout="position">
      {items.map((item) => (
        <motion.li
          key={item.id}
          variants={itemVariants}
          initial="hidden"
          animate="visible"
          exit="hidden"
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
        >
          {item.content}
        </motion.li>
      ))}
    </motion.ul>
  );
}

核心代码就这几行,重点在这些细节

这里有几个关键点要特别注意:

  1. layout属性:我把layout从默认的”size”改成了”position”,这个改动立马见效。原因是size会同时触发布局和绘制,而position只会影响位置计算。
  2. transition配置:原本用的是默认的tween动画,在低端机上表现很糟糕。换成spring后,利用物理引擎的自然过渡效果,性能提升很明显。
  3. stiffness和damping:这两个参数我调了很久,300和30的组合在大部分设备上表现都不错。数值太大会显得生硬,太小又容易抖动。

这里有个小插曲,我一开始把transition写在variants里面,结果发现动画效果不对。折腾了半天发现,当需要覆盖默认动画时,transition应该放在组件属性里,而不是variants定义中。

踩坑提醒:这三点一定注意

分享几个血泪教训:

  • 别一股脑全用layout特性,按需使用”position”或”size”能省不少性能开销
  • 记得给父容器设置will-change: transform,这个对GPU加速很有帮助
  • 复杂的连续动画最好拆分成多个独立动画,避免过度渲染

说个尴尬的事,最开始我还怀疑是服务器响应慢导致的卡顿,特意优化了接口性能。fetch(‘https://jztheme.com/api/data’)改成并发请求后才发现跟动画卡顿完全没关系,白忙活了一天。

还有点小瑕疵,但无伤大雅

虽然主要问题解决了,但现在快速滑动时偶尔还是会有一点点掉帧。不过已经比之前强太多了,至少在主流机型上都能流畅运行。

另外我发现当列表项超过50个时,初次渲染还是会有点卡顿。暂时的解决方案是做懒加载,分批渲染数据:

const [visibleItems, setVisibleItems] = useState(10);

useEffect(() => {
  const handleScroll = () => {
    if (window.scrollY + window.innerHeight >= document.body.offsetHeight - 100) {
      setVisibleItems((prev) => prev + 10);
    }
  };
  
  window.addEventListener("scroll", handleScroll);
  return () => window.removeEventListener("scroll", handleScroll);
}, []);

// 在渲染时
{items.slice(0, visibleItems).map(...)}

以上是我踩坑后的总结

总的来说,Framer Motion确实是个很强大的动画库,但在移动端使用时还是要多注意性能问题。特别是layout特性和transition配置,这两个是最容易出问题的地方。

这个方案不是最优的,但胜在改动小效果明显。如果你有更好的实现方式欢迎评论区交流。后续我打算研究下怎么结合React.memo进一步优化性能,到时候再跟大家分享。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论