结论先行拖拽排序分三段:指针跟随、位置计算、位移动画。前两段各 60 行, 第三段用 FLIP——先量后动,比直接 transition 位置顺滑得多。
不引入库的原因是需求很窄:单列、不允许跨组、不要占位阴影。库带来的配置比代码多。
01180 行的结构
1function onPointerDown(e: PointerEvent) {
2 rects = items.map(el => el.getBoundingClientRect()); // 先量
3 draggingIndex = indexOf(e.target as HTMLElement);
4}
5
6function onPointerMove(e: PointerEvent) {
7 const target = indexFromY(e.clientY); // 算出该插到哪
8 if (target !== draggingIndex) move(draggingIndex, target);
9}
02FLIP 为什么顺滑
FLIP = First, Last, Invert, Play。先记录元素初始位置(First),DOM 移动后记录新位置(Last), 用 transform 把它瞬间挪回原位(Invert),再动画到 0(Play)。 全程只动 transform,不触发布局。
1function play(el: HTMLElement, prev: DOMRect) {
2 const next = el.getBoundingClientRect();
3 const dx = prev.left - next.left;
4 const dy = prev.top - next.top;
5 el.animate(
6 [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'none' }],
7 { duration: 180, easing: 'cubic-bezier(.22,1,.36,1)' },
8 );
9}
03两个取舍
- 用 Pointer Events 而非 mouse/touch,一套代码覆盖两端。
- 只在拖拽期间加 will-change,长期挂着会白占一层合成内存。