将容器内的元素变为可拖拽
概述
在容器内,我们经常需要将里面的元素变为可拖拽的。例如,你可能想要让用户能够通过拖动来重新排列列表中的项目或者移动图片等。
实现方法
新建拖拽实例
javascript
// drag.js
// 将容器内的元素变为可拖拽
const DRAG_ITEM_MASK = "drag__item__mask",
MOVING = "moving";
export class Drag {
constructor(container) {
this.sourceNode = null;
this.dragItemIndex = null; // 拖拽时dom的索引,拖拽开始时确定值
this.sourceIndex = null; // 拖拽过程中当前dom的索引,会随着位置改变而改变
this.targetIndex = null; // 鼠标进入的目标dom的索引,会随着位置改变而改变
this.container = container;
this._init();
}
_init() {
const _this = this;
let children = Array.from(this.container.children);
_this._setDraggable(children, true);
this.container.ondragstart = this._ondragstart.bind(this);
this.container.ondragenter = this._ondragenter.bind(this);
this.container.ondragend = this._ondragend.bind(this);
}
// 拖动开始
_ondragstart(e) {
if (!e.target.draggable) return;
let children = Array.from(this.container.children);
this._createMask(children);
setTimeout(() => {
e.target.classList.add(MOVING);
const mask = e.target.querySelector(`.${DRAG_ITEM_MASK}`);
mask.style.background = "#fff";
mask.style.border = "2px dashed #ff0000";
}, 0);
this.sourceNode = e.target;
this.dragItemIndex = children.indexOf(e.target);
}
// 进入某一个可拖动项
_ondragenter(e) {
if (!this.sourceNode || e.target.className !== DRAG_ITEM_MASK) {
return;
}
const container = this.container;
let targetNode = e.target.parentElement;
let sourceNode = this.sourceNode;
if (targetNode === container || targetNode === sourceNode) {
return;
}
let children = Array.from(container.children);
this.sourceIndex = children.indexOf(sourceNode);
this.targetIndex = children.indexOf(targetNode);
if (this.targetIndex < 0) return;
if (this.sourceIndex < this.targetIndex) {
// 向下拖动
container.insertBefore(sourceNode, targetNode.nextElementSibling);
} else {
// 向上拖动
container.insertBefore(sourceNode, targetNode);
}
}
// 拖动结束
_ondragend(e) {
if (!e.target.draggable) return;
e.target.classList.remove(MOVING);
let children = Array.from(this.container.children);
this._removeMask(children);
const targetIndex = children.indexOf(e.target);
this.sourceNode = null;
this.ondragend &&
this.ondragend({
sourceIndex: this.dragItemIndex,
targetIndex: targetIndex,
});
}
_setDraggable(elements, bool = true) {
elements.forEach((element) => {
if (!element.dataset.nodrag) {
element.draggable = bool;
element.style.cursor = bool ? "move" : "default";
}
});
}
_createMask(elements = []) {
elements.forEach((el) => {
if (!el.dataset.nodrag) {
const mask = document.createElement("div");
mask.className = DRAG_ITEM_MASK;
mask.style.position = "absolute";
mask.style.inset = 0;
mask.style.zIndex = 9999;
el.style.position = "relative";
el.appendChild(mask);
}
});
}
_removeMask(elements = []) {
elements.forEach((el) => {
const mask = el.querySelector(`.${DRAG_ITEM_MASK}`);
mask?.remove();
});
}
distroy() {
if (this.container) {
let children = Array.from(this.container.children);
this._setDraggable(children, null);
this.container.ondragstart = null;
this.container.ondragenter = null;
this.container.ondragend = null;
this.ondragend = null;
}
}
}
使用示例
在指令中使用
js
const drag = {
mounted(el, { value }, vnode) {
// 指令绑定到元素时调用
if (!!value === true) {
const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
el.drag = new Drag(el);
el.drag.ondragend = onDragChange;
}
},
updated(el, { value }, vnode) {
// 元素更新前调用
if (!!value === false && el.drag) {
el.drag?.distroy();
el.drag = null;
} else if (!!value === true) {
const onDragChange = vnode.props["on:dragChange"] ?? (() => {});
el.drag = new Drag(el);
el.drag.ondragend = onDragChange;
}
},
beforeUnmount(el) {
// 元素解绑时调用
el.drag?.distroy();
el.drag = null;
},
};
在组件中使用指令 v-drag
vue
<div class="drag-box" v-drag="true"></div>