网站页面设计的网址,需要网站建设,网站付费推广竞价,定制app网站新的学习方法
用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用
从两个DEMO开始
Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC
列表排序的三个阶段
拖动开始#xff08;dragstart#x…新的学习方法
用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用
从两个DEMO开始
Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC
列表排序的三个阶段
拖动开始dragstart 被拖动图层的状态变化会出一个浮层 拖动进行中(dragmove) 浮层会随着鼠标移动条目发生换位当浮层下沿超过被拖条目二分之一的时候触发换位。这是一个比较独特的需求 松开鼠标阶段drop 浮层消失被拖动图层状态复原数据被更新
拖动排序功能开发
第一阶段 Dragstart
被拖动图层的状态变化 常规做法 添加mouseDown事件检查当前的target是那个元素然后给他添加特定的状态添加mouseMove事件创一个和被拖动元素一模一样的的浮层将它的定位设置 它的定位为绝对定位并且随着鼠标的坐标更新。 使用HTML的Drag特性 文档地址拖拽操作浏览器的默认拖拽行为支持图象链接和选择的文本其他元素默认情况是不可拖拽的。如果想可以拖拽可以设置为draggable true使用dragstart事件监控拖动开始并设置对应的属性
LayerList组件中添加draggable属性
// LayerList.vue
liclassant-list-itemv-foritem in list :keyitem.id:class{ active: item.id selectedId }clickhandleClick(item.id)draggabletrue
/li这样就可以有效果了当拖动对应条目的时候它会自动生成半透明的条目并且跟随鼠标的移动。 接下来就开始使用dragstart事件监控拖动开始并设置对应的属性
给被拖动元素添加特定的状态使用一系列的事件来监控拖动的进度使用dragStart开始拖动操作
// LayerList.vue
// html部分
liclassant-list-itemv-foritem in list :keyitem.id:class{ active: item.id selectedId, ghost: dragData.currentDragging item.id}clickhandleClick(item.id)dragstartonDragStart($event, item.id)draggabletrue
/li
// js部分(setup)
const dragData reactive({currentDragging:
})
const onDragStart (e: DragEvent, id: string ) {dragData.currentDragging id;
}
// css部分
.ant-list-item.ghost {opacity: 0.5;
}完成出来的效果 接下来就是在鼠标松开的时候特定的状态消失使用drop事件
ul :listlist classant-list-items ant-list-border droponDrop/ul
const onDrop (e: DragEvent ) {dragData.currentDragging ;
}但是这样做发现不起作用后来发现是onDrog事件并没有触发原因 dragenter 或 dragover 事件的监听程序用于表示有效的放置目标也就是被拖拽项目可能放置的地方。网页或应用程序的大多数区域都不是放置数据的有效位置。因此这些事件的默认处理是不允许放置。 指定放置对象 因为网页大部分区域不是有效的放置位置这些事件的默认处理都是不允许放置所以这个行为并不会被触发。 如果你想要允许放置你必须取消 dragenter 和 dragover 事件来阻止默认的处理。你可以在属性定义的事件监听程序返回 false或者调用事件的 preventDefault() 方法来实现这一点。在一个独立脚本中的定义的函数里可能后者更可行。 最终添加阻止默认行为事件
ul :listlist classant-list-items ant-list-border droponDrop dragoveronDragOverconst onDragOver (e: DragEvent) {e.preventDefault()
}处理松开鼠标时进行排序 修改dragData 添加一个当前索引的属性 const dragData reactive({currentDragging: ,currentIndex: -1,});dragstart“onDragStart($event, item.id, index)” 方法中多添加一个index参数 const onDragStart (e: DragEvent, id: string, index: number) {dragData.currentDragging id;dragData.currentIndex index;};有了开始拖动的index之后我们要知道drop的时候新的index我们怎么在onDrop方法中拿到新的index呢因为在onDrop中我们的参数是event使用event.target可以拿到dom元素把最新的index放到dom元素上面就可以了使用HTMLElement.dataset 3. 使用 HTMLElement.dataset拿到最新的索引 HTMLElement.dataset属性允许无论是在读取模式和写入模式下访问在HTML 或 DOM中元素上设置的 所有自定义数据属性data-*集 它是一个DOMString的映射每个自定义数据属性的一个条目。 请注意dataset属性本身可以被读取但不能直接写入相反所有的写入必股友是它的属性这反过来 表示数据属性。 还要注意一个HTML data-attribute 及其对应的DOM dataset.property 不共享相同的名称但它 们总是相似的 liclassant-list-item:class{active: item.id selectedId,ghost: dragData.currentDragging item.id,}v-for(item, index) in list:keyitem.idclickhandleClick(item.id)dragstartonDragStart($event, item.id, index):data-indexindexdraggabletrue修改onDrop事件
const onDrop (e: DragEvent) {const currentEle e.target as HTMLElement;if (currentEle.dataset.index) {const moveIndex parseInt(currentEle.dataset.index);console.log(moveIndex);}dragData.currentDragging ;
};但是这样写moveIndex是不一定存在的因为e.target是鼠标指向的元素所以当在目标子元素上面进行释放的话就会把目标当成子元素比如如果释放到的元素是锁元素则currentEle就是锁元素。所以这里需要一个方法来向上进行检索找到符合条件的父元素。
export const getParentElement (element: HTMLElement, className: string) {while (element) {if (element.classList element.classList.contains(className)) {return element;} else {element element.parentNode as HTMLElement;}}return null;
};const onDrop (e: DragEvent) {const currentEle getParentElement(e.target as HTMLElement,ant-list-item);if (currentEle currentEle.dataset.index) {const moveIndex parseInt(currentEle.dataset.index);// 使用第三方库arrayMove改变数组arrayMove.mutate(props.list, dragData.currentIndex, moveIndex);}dragData.currentDragging ;};array-move 最终实现的效果
在拖动时完成排序 const onDragEnter (e: DragEvent, index: number) {// 这里的判断是为了避免完成转换后触发新的一次dragEnter事件if (index ! dragData.currentIndex) {console.log(enter, index, dragData.currentIndex);arrayMove.mutate(props.list, dragData.currentIndex, index);dragData.currentIndex index;end index}};这样就可以在拖动时完成排序了onDrop里面就不需要进行同样的操作了修改一下onDrop事件
let start -1;
let end -1;
const onDragStart (e: DragEvent, id: string, index: number) {dragData.currentDragging id;dragData.currentIndex index;start index;
};
const onDrop (e: DragEvent) {context.emit(drop, { start, end})dragData.currentDragging ;
};现在就完成了可拖动排序的简单编码主要掌握三个阶段
排序开始监控被拖拽的元素添加特殊状态和UI移动阶段进入别的列表的时候完成数据的交换drop阶段松开按钮的时候将状态恢复原状并且发送对应的事件。
使用第三方库进行排序
使用Vue Draggable进行排序
vue.draggable.next
npm i -S vuedraggablenext将用draggable替换掉ul
templatedraggable:listlistclassant-list-items ant-list-borderedghost-classghosthandle.handleitem-keyidtemplate #item{ element }liclassant-list-item:class{ active: element.id selectedId }clickhandleClick(element.id)a-tooltip :titleelement.isHidden ? 显示 : 隐藏a-buttonshapecircleclick.stophandleChange(element.id, isHidden, !element.isHidden)template v-slot:icon v-ifelement.isHiddenEyeInvisibleOutlined //templatetemplate v-slot:icon v-elseEyeOutlined / /template/a-button/a-tooltipa-tooltip :titleelement.isLocked ? 解锁 : 锁定a-buttonshapecircleclick.stophandleChange(element.id, isLocked, !element.isLocked)template v-slot:icon v-ifelement.isLockedLockOutlined//templatetemplate v-slot:icon v-elseUnlockOutlined / /template/a-button/a-tooltipinline-editclassedit-area:valueelement.layerNamechange(value) {handleChange(element.id, layerName, value);}/inline-edita-tooltip title拖动排序a-button shapecircle classhandletemplate v-slot:iconDragOutlined / /template/a-button/a-tooltip/li/template/draggable
/template
script langts
import { defineComponent, PropType } from vue;
import draggable from vuedraggable;
import {EyeOutlined,EyeInvisibleOutlined,LockOutlined,UnlockOutlined,DragOutlined,
} from ant-design/icons-vue;
import { ComponentData } from ../store/editor;
import InlineEdit from ../components/InlineEdit.vue;
export default defineComponent({props: {list: {type: Array as PropTypeComponentData[],required: true,},selectedId: {type: String,required: true,},},emits: [select, change, drop],components: {EyeOutlined,EyeInvisibleOutlined,LockOutlined,UnlockOutlined,InlineEdit,draggable,DragOutlined,},setup(props, context) {const handleClick (id: string) {context.emit(select, id);};const handleChange (id: string, key: string, value: boolean) {const data {id,key,value,isRoot: true,};context.emit(change, data);};return {handleChange,handleClick,};},
});
/scriptstyle scoped
.ant-list-item {padding: 10px 15px;transition: all 0.5s ease-out;cursor: pointer;justify-content: normal;border: 1px solid #fff;border-bottom-color: #f0f0f0;
}
.ant-list-item.active {border: 1px solid #1890ff;
}
.ant-list-item.ghost {opacity: 0.5;
}.ant-list-item:hover {background: #e6f7ff;
}
.ant-list-item * {margin-right: 10px;
}
.ant-list-item button {font-size: 12px;
}.ant-list-item .handle {cursor: move;margin-left: auto;
}
.ant-list-item .edit-area {width: 100%;
}
/style