html5 drag实现拖拽 您所在的位置:网站首页 div拖拽库 html5 drag实现拖拽

html5 drag实现拖拽

2023-11-07 16:18| 来源: 网络整理| 查看: 265

HTML5 拖放 (DnD) API 可以实现拖动功能,下面具体看如何实现。

原理

对元素设置设置 draggable=true可以使元素支持拖动,并且通过dragstart、dragover、drop来实现简单的拖动功能。

item 1 item 2 item 3 item 4 item 5 item 6 .grid { display: grid; gap: 20px 80px; grid-template-columns: auto auto auto auto; } .item { border: 1px solid #2196f3; background-color: #d3eafd; border-radius: 5px; padding: 10px; cursor: move; user-select: none; } var items = document.querySelectorAll(".item"); var dragEl; function handleDragStart() { dragEl = this; } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } return false; } function handleDrop() { document.querySelector(".grid").insertBefore(dragEl, this); } items.forEach(function (item, index) { item.addEventListener("dragstart", handleDragStart); item.addEventListener("dragover", handleDragOver); item.addEventListener("drop", handleDrop); });

效果: 这里使用insertBefore将拖动的元素放到目标元素前面。

优点

实现简单,性能好。

缺点 不能自定义跟随鼠标透明元素的样式 不能自定义设置拖拽过程中的鼠标指针 不支持触摸拖拽 优化

在实际项目中这么简单实现拖动肯定不行,需要对其细节效果进行优化。

拖动元素添加响应效果

拖动元素添加透明度来和其他元素进行区分。

var items = document.querySelectorAll(".item"); var dragEl; function handleDragStart() { dragEl = this; this.style.opacity = "0.5"; } function handleDragEnd() { this.style.opacity = "1"; } function handleDragOver() { if (e.preventDefault) { e.preventDefault(); } return false; } function handleDrop() { document.querySelector(".grid").insertBefore(dragEl, this); } items.forEach(function (item, index) { item.addEventListener("dragstart", handleDragStart); item.addEventListener("dragend", handleDragEnd); item.addEventListener("dragover", handleDragOver); item.addEventListener("drop", handleDrop); });

效果:

拖动到其他元素添加响应效果

拖动到其他元素添加边框给以区分。

.grid { display: grid; gap: 20px 80px; grid-template-columns: auto auto auto auto; } .item { border: 1px solid #2196f3; background-color: #d3eafd; border-radius: 5px; padding: 10px; cursor: move; user-select: none; margin: 1px; } .enter { border: 2px solid #2196f3; margin: 0; } var items = document.querySelectorAll(".item"); var dragEl; function handleDragStart() { dragEl = this; this.style.opacity = "0.5"; } function handleDragEnd() { this.style.opacity = "1"; } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } return false; } function handleDrop() { document.querySelector(".grid").insertBefore(dragEl, this); this.classList.remove("enter"); } function handleDragEnter() { if (this !== dragEl) { this.classList.add("enter"); } } function handleDragLeave() { this.classList.remove("enter"); } items.forEach(function (item, index) { item.addEventListener("dragstart", handleDragStart); item.addEventListener("dragend", handleDragEnd); item.addEventListener("dragover", handleDragOver); item.addEventListener("drop", handleDrop); item.addEventListener("dragenter", handleDragEnter); item.addEventListener("dragleave", handleDragLeave); });

效果: 通过dragend和dragleave事件对元素添加样式。

自定义跟随鼠标透明元素样式

拖动时跟随鼠标指针的那个元素样式过分简陋,虽然HTML5 拖放 API并没有提供方法自定义跟随鼠标的元素样式,但可以用另一种方式去实现。可以利用setDragImage方法。

发生拖动时,从拖动目标 (dragstart事件触发的元素) 生成半透明图像,并在拖动过程中跟随鼠标指针。这个图片是自动创建的,你不需要自己去创建它。然而,如果想要设置为自定义图像,那么 DataTransfer.setDragImage() 方法就能派上用场。 --MDN

可以利用setDragImage设置透明图片,将拖动原生的跟随鼠标指针的那个元素去掉,然后自定一个元素来跟随鼠标移动。

在dragstart事件中setDragImage方法设置透明图片

var img = new Image(); img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E"; e.dataTransfer.setDragImage(img, 0, 0);

然后用cloneNode将元素复制一个新节点对象。

cloneDragEl = dragEl.cloneNode(true);

然后给新节点添加定位和设置位置最后添加到boy中。

cloneDragEl.classList.add("drag-over"); let targetOffset = dragEl.getClientRects(); dragElOffset = { left: e.clientX - targetOffset[0].left, top: e.clientY - targetOffset[0].top, width: targetOffset[0].width, }; cloneDragEl.style.width = `${dragElOffset.width}px`; cloneDragEl.style.transform = `translate3d(${targetOffset[0].left}px,${targetOffset[0].top}px,0)`; document.body.appendChild(cloneDragEl);

在grid元素的dragover事件中根据鼠标的位置来设置复制元素的位置。

function handleGridDragOver(e) { if (e.preventDefault) { e.preventDefault(); } if (cloneDragEl) { cloneDragEl.style.transform = `translate3d(${ e.clientX - dragElOffset.left }px,${e.clientY - dragElOffset.top}px,0)`; } return false; }

在dragend事件中复制元素移除。

function handleDragEnd() { this.style.opacity = "1"; document.body.removeChild(cloneDragEl); }

效果:

完整代码:

.grid { display: grid; gap: 20px 80px; grid-template-columns: auto auto auto auto; } .item { border: 1px solid #2196f3; background-color: #d3eafd; border-radius: 5px; padding: 10px; cursor: move; user-select: none; margin: 1px; } .enter { border: 2px solid #2196f3; margin: 0; } .drag-over { position: fixed; top: 0; left: 0; pointer-events: none; box-sizing: border-box; } var items = document.querySelectorAll(".item"); var dragEl = null; var cloneDragEl = null; function handleDragStart(e) { dragEl = this; this.style.opacity = "0.5"; var img = new Image(); img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E"; e.dataTransfer.setDragImage(img, 0, 0); cloneDragEl = dragEl.cloneNode(true); cloneDragEl.classList.add("drag-over"); let targetOffset = dragEl.getClientRects(); dragElOffset = { left: e.clientX - targetOffset[0].left, top: e.clientY - targetOffset[0].top, width: targetOffset[0].width, }; cloneDragEl.style.width = `${dragElOffset.width}px`; cloneDragEl.style.transform = `translate3d(${targetOffset[0].left}px,${targetOffset[0].top}px,0)`; document.body.appendChild(cloneDragEl); } function handleDragEnd() { this.style.opacity = "1"; document.body.removeChild(cloneDragEl); } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } return false; } function handleDrop() { document.querySelector(".grid").insertBefore(dragEl, this); this.classList.remove("enter"); } function handleDragEnter() { this.classList.add("enter"); } function handleDragLeave() { this.classList.remove("enter"); } function handleGridDragOver(e) { if (e.preventDefault) { e.preventDefault(); } if (cloneDragEl) { cloneDragEl.style.transform = `translate3d(${ e.clientX - dragElOffset.left }px,${e.clientY - dragElOffset.top}px,0)`; } return false; } items.forEach(function (item, index) { item.addEventListener("dragstart", handleDragStart); item.addEventListener("dragend", handleDragEnd); item.addEventListener("dragover", handleDragOver); item.addEventListener("drop", handleDrop); item.addEventListener("dragenter", handleDragEnter); item.addEventListener("dragleave", handleDragLeave); }); document .querySelector(".grid") .addEventListener("dragover", handleGridDragOver);

这里鼠标跟随的元素需要设置样式 pointer-events: none;让元素不会触发鼠标事件。

设置拖拽过程中的鼠标指针

可以通过effectAllowed来设置拖拽过程中的鼠标指针。

e.dataTransfer.effectAllowed = 'move';

但只有预设的几个指针样式,自定义还没办法,知道的同学可以留言告知。

insertBefore方式有点生硬,加上动画效果

首先添加过渡属性。

.item { border: 1px solid #2196f3; background-color: #d3eafd; border-radius: 5px; padding: 10px; cursor: move; user-select: none; margin: 1px; transition: transform 0.3s; }

在dragstart事件中把元素隐藏,并且记录元素的位置。

function handleDragStart(e) { var offset = dragEl.getClientRects(); this.style.opacity = "0"; targetOffset = { left: offset[0].left, top: offset[0].top, }; }

在dragenter事件当鼠标进入元素时触发,改变元素的transform移动到拖拽元素的位置上,也就是targetOffset记录的位置。

function handleDragEnter() { if (dragEl !== this) { this.classList.remove("drag"); let offset = this.getClientRects(); let translate = getTranslate(this); this.style.transform = `translate3d(${ targetOffset.left - offset[0].left + translate.x.value }px,${targetOffset.top - offset[0].top + translate.y.value}px,0)`; targetOffset = { left: offset[0].left, top: offset[0].top, }; } }

这里封装了一个getTranslate函数,getTranslate函数是用来获取元素的translate值,getClientRects获取的是元素在页面视图中的位置,translate是相对初始位置的偏移,获取元素的初始位置(getClientRects获取位置-translate的偏移)就要获取元素的translate偏移值。

function getTranslate(dom) { const attrs = dom.attributeStyleMap.get("transform"); if (!attrs) return { x: { value: 0 }, y: { value: 0 } }; const translation = Array.from(attrs.values()).find( (attr) => attr instanceof CSSTranslate ); return translation; }

这里通过attributeStyleMap来取元素的样式Map。

HTMLElement.attributeStyleMap 只读

一个StylePropertyMap,代表元素的样式属性的声明 --MDN

兼容性: Firefox不支持该属性。

dragend事件中将拖动元素定位到最后位置上。

function handleDragEnd() { let offset = this.getClientRects(); let translate = getTranslate(this); this.style.transform = `translate3d(${ targetOffset.left - offset[0].left + translate.x.value }px,${targetOffset.top - offset[0].top + translate.y.value}px,0)`; this.style.opacity = "1"; document.body.removeChild(cloneDragEl); dragEl = null; cloneDragEl = null; }

完整代码:

.grid { display: grid; gap: 20px 80px; grid-template-columns: auto auto auto auto; } .item { border: 1px solid #2196f3; background-color: #d3eafd; border-radius: 5px; padding: 10px; cursor: move; user-select: none; margin: 1px; transition: transform 0.3s; } .enter { border: 2px solid #2196f3; margin: 0; } .drag-over { position: fixed; top: 0; left: 0; pointer-events: none; box-sizing: border-box; transition: none; } .drag { transition: none; } var items = document.querySelectorAll(".item"); var dragEl = null; var cloneDragEl = null; var targetOffset = null; function getTranslate(dom) { const attrs = dom.attributeStyleMap.get("transform"); if (!attrs) return { x: { value: 0 }, y: { value: 0 } }; const translation = Array.from(attrs.values()).find( (attr) => attr instanceof CSSTranslate ); return translation; } function handleDragStart(e) { dragEl = this; this.classList.add("drag"); cloneDragEl = dragEl.cloneNode(true); this.style.opacity = "0"; var img = new Image(); img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' %3E%3Cpath /%3E%3C/svg%3E"; e.dataTransfer.setDragImage(img, 0, 0); e.dataTransfer.effectAllowed = "move"; cloneDragEl.classList.add("drag-over"); var offset = dragEl.getClientRects(); targetOffset = { left: offset[0].left, top: offset[0].top, }; dragElOffset = { left: e.clientX - offset[0].left, top: e.clientY - offset[0].top, width: offset[0].width, }; cloneDragEl.style.width = `${dragElOffset.width}px`; cloneDragEl.style.transform = `translate3d(${targetOffset.left}px,${targetOffset.top}px,0)`; document.body.appendChild(cloneDragEl); } function handleDragEnd() { let offset = this.getClientRects(); let translate = getTranslate(this); this.style.transform = `translate3d(${ targetOffset.left - offset[0].left + translate.x.value }px,${targetOffset.top - offset[0].top + translate.y.value}px,0)`; this.style.opacity = "1"; document.body.removeChild(cloneDragEl); dragEl = null; cloneDragEl = null; } function handleDragOver(e) { if (e.preventDefault) { e.preventDefault(); } return false; } function handleGridDragOver(e) { if (e.preventDefault) { e.preventDefault(); } if (cloneDragEl) { cloneDragEl.style.transform = `translate3d(${ e.clientX - dragElOffset.left }px,${e.clientY - dragElOffset.top}px,0)`; } return false; } function handleDrop(e) { e.stopPropagation(); document.body.removeChild(cloneDragEl); dragEl = null; cloneDragEl = null; } function handleDragEnter() { if (dragEl !== this) { this.classList.remove("drag"); // this.classList.add("enter"); let offset = this.getClientRects(); let translate = getTranslate(this); this.style.transform = `translate3d(${ targetOffset.left - offset[0].left + translate.x.value }px,${targetOffset.top - offset[0].top + translate.y.value}px,0)`; targetOffset = { left: offset[0].left, top: offset[0].top, }; } } items.forEach(function (item, index) { item.addEventListener("dragstart", handleDragStart); item.addEventListener("dragend", handleDragEnd); item.addEventListener("dragover", handleDragOver); item.addEventListener("drop", handleDrop); item.addEventListener("dragenter", handleDragEnter); }); document .querySelector(".grid") .addEventListener("dragover", handleGridDragOver); 思考

除了上面的优化,还有很多优化的点,比如:

如何获取最终的排序列表。 性能上如何优化提高性能。 封装成一个工具插件的话代码结构如何组织,功能配置项如何抽离等。 总结

本文主要是帮助理解拖拽的实现原理,后面会通过解读拖拽工具库源码来总结更多的优化角度。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有