为什么用 transform 做动画还是会卡顿?

FSD-雨诺 阅读 31

我最近在 Vue 里做一个拖拽卡片的效果,为了性能特意用了 transform 来做位移,但快速拖动时还是明显掉帧。网上都说 transform 不会触发重排,应该很流畅才对,是不是我哪里写错了?

我试过把 will-change: transform 加上,也用了 requestAnimationFrame,但效果不明显。下面是我简化后的代码:

<template>
  <div 
    class="card" 
    :style="{ transform: translate(${x}px, ${y}px) }"
    @mousedown="startDrag"
  >
    拖我
  </div>
</template>

难道是频繁更新响应式数据导致的?求指点!

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
诸葛丹丹
这个问题挺典型的,很多人以为用了 transform 就万事大吉了,但其实坑不在 CSS 这边,而是在 Vue 的响应式系统这边。

你频繁更新的是响应式数据 xy,每次数据变化 Vue 都要走一遍响应式触发 → 重新渲染 → VNode 对比 → patch 的流程。这里面大量的 JavaScript 计算开销才是拖慢你的真正原因,CSS 层面的优化完全被 Vue 的框架开销吃掉了。

解决思路很简单:绕过 Vue 的响应式系统,直接操作原生 DOM。

首先别用响应式数据驱动样式,而是在 rAF 回调里直接改 DOM 的 style:





几个关键点解释一下:

第一,用 ref 获取 DOM 引用而不是通过 document.querySelector,这样更符合 Vue 的写法。

第二,位置坐标用普通变量 posX posY 存储,完全不参与 Vue 的响应式追踪,这样更新它们没有任何框架开销。

第三,requestAnimationFrame 这里的作用是节流,把 mousemove 期间可能每秒触发 60-120 次的 DOM 写入合并成每秒 60 次。写入操作少了,浏览器合成层压力也小。

第四,最核心的就是直接改 cardRef.value.style.transform,这一步是纯原生 DOM 操作,不经过 Vue 的任何生命周期和虚拟 DOM 对比。

另外提醒一下,will-change 这个属性其实不适合在运行时动态加,浏览器需要时间准备合成层。你可以在 CSS 里直接写死 will-change: transform,或者干脆不加,现代浏览器对 transform 的合成优化已经很强了。

这样改完以后,拖拽应该能跑满 60fps 了。
点赞
2026-03-19 14:00
UX树恺
UX树恺 Lv1
你猜对了,问题确实出在频繁更新响应式数据上。

虽然 transform 本身不触发重排,性能很好,但你每次修改 xy,Vue 都要进行依赖收集、触发 Virtual DOM 的 diff 计算,甚至可能引发组件的局部重渲染。鼠标移动事件一秒触发几十上百次,Vue 的响应式开销累加起来就成了性能杀手。

解决方案很简单:在拖动过程中,直接操作 DOM,绕过 Vue 的数据响应式系统。等拖动结束了,再同步数据状态。

试试这个改法:





这么改之后,拖动过程中完全绕过了 Vue 的更新机制,只走原生 DOM API,流畅度立马就不一样了。will-changerequestAnimationFrame 其实属于锦上添花,响应式数据频繁更新这个根本问题不解决,加再多优化手段也没用。
点赞 3
2026-03-01 19:11