拖拽元素时卡顿严重,怎么优化性能?

闲人梦雅 阅读 20

我在做一个看板功能,用原生 drag 和 drop API 实现卡片拖拽,但元素一多就特别卡,尤其在移动过程中明显掉帧。

试过用 transform: translate() 替代 top/left,也加了 will-change: transform,但效果不明显。是不是监听的事件太频繁了?

element.addEventListener('dragover', (e) => {
  e.preventDefault();
  // 这里频繁触发,可能影响性能?
  updateDropIndicator(e.clientY);
});
我来解答 赞 3 收藏
二维码
手机扫码查看
2 条解答
一淑怡
一淑怡 Lv1
拖拽卡顿大概率不是 transform 或 will-change 的问题,核心是 dragover 事件在移动过程中每秒触发几十次,你每次都在做 DOM 操作(比如 updateDropIndicator),这就炸了。

建议改成用 requestAnimationFrame 做节流,把高频事件“拍平”成帧级更新:

let pendingUpdate = null;

function scheduleUpdate(y) {
if (!pendingUpdate) {
pendingUpdate = requestAnimationFrame(() => {
updateDropIndicator(pendingUpdate.y);
pendingUpdate = null;
});
}
pendingUpdate.y = y;
}

element.addEventListener('dragover', (e) => {
e.preventDefault();
scheduleUpdate(e.clientY);
});


另外,dragenter 和 dragleave 也别乱用,它们会疯狂触发,尤其在嵌套元素间。如果只是想高亮当前目标,直接用 dragover + pointer-events 控制就行。

还有一点容易被忽略:别在 dragover 里做任何重排(reflow)操作,比如读 offsetTop、clientHeight 之类的,尽量把读写分离。如果必须读,统一在 animationFrame 里先 collect,再 apply。

最后,真要极致优化,可以考虑直接放弃原生 drag&drop,用 mousedown + mousemove + mouseup 自己实现,完全掌控事件流和渲染节奏——很多看板类工具都是这么干的,虽然多写点代码,但性能稳如老狗。
点赞 1
2026-02-24 20:25
Designer°东正
代码给你,直接上优化方案:

你卡的根本原因不是 will-changetransform,是 dragover 事件每秒触发几十次,每次都在做 DOM 操作、布局计算、重绘重排,浏览器扛不住。

先解决几个关键点:

1. dragover 里别做任何重活,只缓存坐标,别直接操作 DOM
2. 用 requestAnimationFrame 做节流,把视觉更新统一挂到下一帧
3. drag 元素用 fixed 定位 + pointer-events: none,拖动时用一个 ghost 元素跟着动,别动原节点

具体改造:

let pendingUpdate = false;
let lastY = 0;

element.addEventListener('dragover', (e) => {
e.preventDefault();
lastY = e.clientY;
if (!pendingUpdate) {
pendingUpdate = true;
requestAnimationFrame(() => {
updateDropIndicator(lastY);
pendingUpdate = false;
});
}
});


然后 dragstart 里创建一个独立的拖动元素,别动原 DOM:

element.addEventListener('dragstart', (e) => {
e.dataTransfer.effectAllowed = 'move';
const ghost = document.createElement('div');
ghost.className = 'drag-ghost';
ghost.style.position = 'fixed';
ghost.style.left = e.clientX + 'px';
ghost.style.top = e.clientY + 'px';
ghost.style.zIndex = '9999';
ghost.style.pointerEvents = 'none';
ghost.innerHTML = element.innerHTML;
document.body.appendChild(ghost);

e.dataTransfer.setDragImage(ghost, 0, 0);

element.addEventListener('dragend', () => {
ghost.remove();
}, { once: true });
});


最后,drop 时只更新数据,别直接操作 DOM,等数据层刷新视图。

这样拖动时几乎不触发重排,帧率稳了。

真要更狠一点,用 pointer events + 自己写拖拽逻辑,别用原生 drag & drop,那玩意就是个性能黑洞。
点赞 2
2026-02-24 00:03