先来看效果
index.html文件
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><link rel="stylesheet" href="./index.css" /></head><body><div class="list"><div draggable="true" class="list-item">1</div><div draggable="true" class="list-item">2</div><div draggable="true" class="list-item">3</div><div draggable="true" class="list-item">4</div><div draggable="true" class="list-item">5</div></div><script src="./Flip.js"></script><script src="./index.js"></script></body>
</html>
index.css文件
* {margin: 0;padding: 0;box-sizing: border-box;
}body {height: 100vh;display: flex;justify-content: center;align-items: center;
}.list {width: 500px;
}.list-item {margin: 5px 0;padding: 0 20px;line-height: 40px;height: 40px;background: linear-gradient(to right, #267871, #136a8a);color: #fff;cursor: move;user-select: none;border-radius: 5px;
}
.list-item.moving {background: transparent;color: transparent;border: 1px dashed #ccc;
}
index.js文件
const list = document.querySelector('.list');
let sourceNode; // 当前正在拖动的是哪个元素
let flip;
list.ondragstart = (e) => {setTimeout(() => {e.target.classList.add('moving');}, 0);sourceNode = e.target;e.dataTransfer.effectAllowed = 'move';flip = new Flip(list.children, 0.3);
};
list.ondragover = (e) => {e.preventDefault();
};
list.ondragenter = (e) => {e.preventDefault();if (e.target === list || e.target === sourceNode) {return;}const children = Array.from(list.children);const sourceIndex = children.indexOf(sourceNode);const targetIndex = children.indexOf(e.target);if (sourceIndex < targetIndex) {list.insertBefore(sourceNode, e.target.nextElementSibling);} else {list.insertBefore(sourceNode, e.target);}flip.play();
};list.ondragend = (e) => {e.target.classList.remove('moving');
};
Flip.js文件
const Flip = (function () {class FlipDom {constructor(dom, duration = 0.5) {this.dom = dom;this.transition =typeof duration === 'number' ? `${duration}s` : duration;this.firstPosition = {x: null,y: null,};this.isPlaying = false;this.transitionEndHandler = () => {this.isPlaying = false;this.recordFirst();};}getDomPosition() {const rect = this.dom.getBoundingClientRect();return {x: rect.left,y: rect.top,};}recordFirst(firstPosition) {if (!firstPosition) {firstPosition = this.getDomPosition();}this.firstPosition.x = firstPosition.x;this.firstPosition.y = firstPosition.y;}*play() {if (!this.isPlaying) {this.dom.style.transition = 'none';const lastPosition = this.getDomPosition();const dis = {x: lastPosition.x - this.firstPosition.x,y: lastPosition.y - this.firstPosition.y,};if (!dis.x && !dis.y) {return;}this.dom.style.transform = `translate(${-dis.x}px, ${-dis.y}px)`;yield 'moveToFirst';this.isPlaying = true;}this.dom.style.transition = this.transition;this.dom.style.transform = `none`;this.dom.removeEventListener('transitionend', this.transitionEndHandler);this.dom.addEventListener('transitionend', this.transitionEndHandler);}}class Flip {constructor(doms, duration = 0.5) {this.flipDoms = [...doms].map((it) => new FlipDom(it, duration));this.flipDoms = new Set(this.flipDoms);this.duration = duration;this.flipDoms.forEach((it) => it.recordFirst());}addDom(dom, firstPosition) {const flipDom = new FlipDom(dom, this.duration);this.flipDoms.add(flipDom);flipDom.recordFirst(firstPosition);}play() {let gs = [...this.flipDoms].map((it) => {const generator = it.play();return {generator,iteratorResult: generator.next(),};}).filter((g) => !g.iteratorResult.done);while (gs.length > 0) {document.body.clientWidth;gs = gs.map((g) => {g.iteratorResult = g.generator.next();return g;}).filter((g) => !g.iteratorResult.done);}}}return Flip;
})();