为什么用 transform 做动画还是会卡顿?
我最近在 Vue 里做一个拖拽卡片的效果,为了性能特意用了 transform 来做位移,但快速拖动时还是明显掉帧。网上都说 transform 不会触发重排,应该很流畅才对,是不是我哪里写错了?
我试过把 will-change: transform 加上,也用了 requestAnimationFrame,但效果不明显。下面是我简化后的代码:
<template>
<div
class="card"
:style="{ transform: translate(${x}px, ${y}px) }"
@mousedown="startDrag"
>
拖我
</div>
</template>
难道是频繁更新响应式数据导致的?求指点!
你频繁更新的是响应式数据
x和y,每次数据变化 Vue 都要走一遍响应式触发 → 重新渲染 → VNode 对比 → patch 的流程。这里面大量的 JavaScript 计算开销才是拖慢你的真正原因,CSS 层面的优化完全被 Vue 的框架开销吃掉了。解决思路很简单:绕过 Vue 的响应式系统,直接操作原生 DOM。
首先别用响应式数据驱动样式,而是在 rAF 回调里直接改 DOM 的 style:
几个关键点解释一下:
第一,用
ref获取 DOM 引用而不是通过document.querySelector,这样更符合 Vue 的写法。第二,位置坐标用普通变量
posXposY存储,完全不参与 Vue 的响应式追踪,这样更新它们没有任何框架开销。第三,
requestAnimationFrame这里的作用是节流,把 mousemove 期间可能每秒触发 60-120 次的 DOM 写入合并成每秒 60 次。写入操作少了,浏览器合成层压力也小。第四,最核心的就是直接改
cardRef.value.style.transform,这一步是纯原生 DOM 操作,不经过 Vue 的任何生命周期和虚拟 DOM 对比。另外提醒一下,will-change 这个属性其实不适合在运行时动态加,浏览器需要时间准备合成层。你可以在 CSS 里直接写死
will-change: transform,或者干脆不加,现代浏览器对 transform 的合成优化已经很强了。这样改完以后,拖拽应该能跑满 60fps 了。
虽然
transform本身不触发重排,性能很好,但你每次修改x和y,Vue 都要进行依赖收集、触发 Virtual DOM 的 diff 计算,甚至可能引发组件的局部重渲染。鼠标移动事件一秒触发几十上百次,Vue 的响应式开销累加起来就成了性能杀手。解决方案很简单:在拖动过程中,直接操作 DOM,绕过 Vue 的数据响应式系统。等拖动结束了,再同步数据状态。
试试这个改法:
这么改之后,拖动过程中完全绕过了 Vue 的更新机制,只走原生 DOM API,流畅度立马就不一样了。
will-change和requestAnimationFrame其实属于锦上添花,响应式数据频繁更新这个根本问题不解决,加再多优化手段也没用。