在前端的开发中,拖拽排序是一项非常常见的需求。本文将介绍如何利用 Custom Elements 实现一个拖拽排序组件,并解决在移动端的兼容性问题。
Custom Elements 简介
Custom Elements 是 Web Components 的一部分,它允许开发者定义自己的 HTML 标签以及其行为。通过自定义 HTML 标签,我们可以实现更为灵活和可复用的组件,同时也有助于提高代码的可维护性和可读性。
实现拖拽排序组件
首先,我们需要定义一个自定义 HTML 标签,并为其添加如下属性。
<my-draggable> <!-- 子元素 --> </my-draggable>
draggable
:是否可拖拽。ondragstart
:拖拽开始时触发的事件。ondragover
:拖拽目标元素上方时触发的事件。ondragend
:拖拽结束时触发的事件。
// javascriptcn.com 代码示例 class MyDraggable extends HTMLElement { constructor() { super(); this.addEventListener('dragstart', this.handleDragStart); this.addEventListener('dragover', this.handleDragOver); this.addEventListener('dragend', this.handleDragEnd); } handleDragStart(e) { // 设置拖拽传输的数据 e.dataTransfer.setData('text/plain', e.target.id); } handleDragEnd(e) { // 判断是否拖拽成功 if (e.dataTransfer.dropEffect === 'move') { // 操作成功 // ... } else { // 操作失败 // ... } } handleDragOver(e) { // 阻止默认事件 e.preventDefault(); // 设置拖拽效果为移动 e.dataTransfer.dropEffect = 'move'; } }
接下来,我们需要为每一个子元素设置可拖拽的属性,并在拖拽结束后根据拖拽的位置进行排序。
// javascriptcn.com 代码示例 class MyDraggable extends HTMLElement { // ... connectedCallback() { const children = [...this.children]; children.forEach((child, index) => { child.setAttribute('draggable', true); child.setAttribute('data-index', index); }); } handleDragEnd(e) { // 判断是否拖拽成功 if (e.dataTransfer.dropEffect === 'move') { // 操作成功 const children = [...this.children]; const sourceIndex = e.target.getAttribute('data-index'); const targetIndex = this.getTargetIndex(e.clientY); if (sourceIndex !== targetIndex) { const sourceChild = children[sourceIndex]; const targetChild = children[targetIndex]; if (targetIndex < sourceIndex) { this.insertBefore(sourceChild, targetChild); } else { this.insertBefore(sourceChild, targetChild.nextSibling); } } } else { // 操作失败 // ... } } getTargetIndex(y) { const children = [...this.children]; return children.findIndex(child => { const { top, height } = child.getBoundingClientRect(); return y > top + height / 2; }); } }
解决移动端兼容性问题
在移动端的浏览器中,拖拽排序功能可能会遇到一些问题。因为移动端通常是通过手指滑动来进行操作,而不是通过鼠标拖拽。因此,我们需要对代码进行一些修改,以适应移动端的操作方式。
首先,我们需要监听触摸相关事件,而不是鼠标事件。
// javascriptcn.com 代码示例 class MyDraggable extends HTMLElement { constructor() { super(); this.addEventListener('touchstart', this.handleTouchStart); this.addEventListener('touchmove', this.handleTouchMove); this.addEventListener('touchend', this.handleTouchEnd); } handleTouchStart(e) { const { targetTouches } = e; if (targetTouches.length === 1) { const { pageX, pageY } = targetTouches[0]; this.startX = pageX; this.startY = pageY; this.isDragging = false; } } handleTouchMove(e) { const { targetTouches } = e; if (targetTouches.length === 1) { const { pageX, pageY } = targetTouches[0]; this.currentX = pageX; this.currentY = pageY; this.isDragging = true; // 设置滚动条 // ... } } handleTouchEnd(e) { if (this.isDragging) { const children = [...this.children]; const sourceIndex = e.target.getAttribute('data-index'); const targetIndex = this.getTargetIndex(this.currentY); if (sourceIndex !== targetIndex && targetIndex >= 0) { const sourceChild = children[sourceIndex]; const targetChild = children[targetIndex]; if (targetIndex < sourceIndex) { this.insertBefore(sourceChild, targetChild); } else { this.insertBefore(sourceChild, targetChild.nextSibling); } } } else { // ... } this.isDragging = false; } // ... }
其次,我们需要手动处理滚动条的滚动。在鼠标拖拽时,浏览器会自动处理滚动条的滚动。但是在触摸拖拽时,浏览器并不会自动处理滚动条的滚动,因此我们需要手动处理。
// javascriptcn.com 代码示例 class MyDraggable extends HTMLElement { // ... handleTouchMove(e) { // ... // 设置滚动条 const { scrollTop } = this; const { clientHeight: height, scrollHeight } = this.parentNode; const { pageY } = targetTouches[0]; const delta = (pageY - this.startY) * 2; if (scrollTop + delta < 0) { this.scrollTop = 0; } else if (scrollTop + delta > scrollHeight - height) { this.scrollTop = scrollHeight - height; } else { this.scrollTop += delta; } } // ... }
示例代码
完整的示例代码如下。
// javascriptcn.com 代码示例 class MyDraggable extends HTMLElement { constructor() { super(); this.addEventListener('dragstart', this.handleDragStart); this.addEventListener('dragover', this.handleDragOver); this.addEventListener('dragend', this.handleDragEnd); this.addEventListener('touchstart', this.handleTouchStart); this.addEventListener('touchmove', this.handleTouchMove); this.addEventListener('touchend', this.handleTouchEnd); } connectedCallback() { const children = [...this.children]; children.forEach((child, index) => { child.setAttribute('draggable', true); child.setAttribute('data-index', index); }); } handleDragStart(e) { // 设置拖拽传输的数据 e.dataTransfer.setData('text/plain', e.target.id); } handleDragEnd(e) { // 判断是否拖拽成功 if (e.dataTransfer.dropEffect === 'move') { // 操作成功 const children = [...this.children]; const sourceIndex = e.target.getAttribute('data-index'); const targetIndex = this.getTargetIndex(e.clientY); if (sourceIndex !== targetIndex) { const sourceChild = children[sourceIndex]; const targetChild = children[targetIndex]; if (targetIndex < sourceIndex) { this.insertBefore(sourceChild, targetChild); } else { this.insertBefore(sourceChild, targetChild.nextSibling); } } } else { // 操作失败 // ... } } handleDragOver(e) { // 阻止默认事件 e.preventDefault(); // 设置拖拽效果为移动 e.dataTransfer.dropEffect = 'move'; } handleTouchStart(e) { const { targetTouches } = e; if (targetTouches.length === 1) { const { pageX, pageY } = targetTouches[0]; this.startX = pageX; this.startY = pageY; this.isDragging = false; } } handleTouchMove(e) { const { targetTouches } = e; if (targetTouches.length === 1) { const { pageX, pageY } = targetTouches[0]; this.currentX = pageX; this.currentY = pageY; this.isDragging = true; // 设置滚动条 const { scrollTop } = this; const { clientHeight: height, scrollHeight } = this.parentNode; const delta = (pageY - this.startY) * 2; if (scrollTop + delta < 0) { this.scrollTop = 0; } else if (scrollTop + delta > scrollHeight - height) { this.scrollTop = scrollHeight - height; } else { this.scrollTop += delta; } } } handleTouchEnd(e) { if (this.isDragging) { const children = [...this.children]; const sourceIndex = e.target.getAttribute('data-index'); const targetIndex = this.getTargetIndex(this.currentY); if (sourceIndex !== targetIndex && targetIndex >= 0) { const sourceChild = children[sourceIndex]; const targetChild = children[targetIndex]; if (targetIndex < sourceIndex) { this.insertBefore(sourceChild, targetChild); } else { this.insertBefore(sourceChild, targetChild.nextSibling); } } } else { // ... } this.isDragging = false; } getTargetIndex(y) { const children = [...this.children]; return children.findIndex(child => { const { top, height } = child.getBoundingClientRect(); return y > top + height / 2; }); } } customElements.define('my-draggable', MyDraggable);
总结
通过这篇文章,我们学习了如何利用 Custom Elements 实现一个拖拽排序组件,并解决在移动端的兼容性问题。通过自定义 HTML 标签以及相关的事件和属性,我们可以轻松实现一个灵活、可复用的组件,提高代码的可维护性和可读性。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/652e151b7d4982a6ebf2592d