LOG // 033 · 编辑器 · 交互

从零实现一个拖拽排序

结论先行拖拽排序分三段:指针跟随、位置计算、位移动画。前两段各 60 行, 第三段用 FLIP——先量后动,比直接 transition 位置顺滑得多。

不引入库的原因是需求很窄:单列、不允许跨组、不要占位阴影。库带来的配置比代码多。

01180 行的结构

drag-sort.tsts
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,不触发布局。

flip.tsts
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,长期挂着会白占一层合成内存。