新的學(xué)習(xí)方法
- 用手寫簡單方法實(shí)現(xiàn)一個(gè)功能
- 然后用比較成熟的第三方解決方案
- 即能學(xué)習(xí)原理又能學(xué)習(xí)第三方庫的使用
從兩個(gè)DEMO開始
- Vue Draggable Next: Vue Draggable Next
- React Sortable HOC: React Sortable HOC
列表排序的三個(gè)階段
- 拖動開始(dragstart)
- 被拖動圖層的狀態(tài)變化
- 會出一個(gè)浮層
- 拖動進(jìn)行中(dragmove)
- 浮層會隨著鼠標(biāo)移動
- 條目發(fā)生換位:當(dāng)浮層下沿超過被拖條目二分之一的時(shí)候,觸發(fā)換位。
這是一個(gè)比較獨(dú)特的需求
- 松開鼠標(biāo)階段(drop)
- 浮層消失
- 被拖動圖層狀態(tài)復(fù)原
- 數(shù)據(jù)被更新
拖動排序功能開發(fā)
第一階段 Dragstart
- 被拖動圖層的狀態(tài)變化 常規(guī)做法
- 添加mouseDown事件,檢查當(dāng)前的target是那個(gè)元素,然后給他添加特定的狀態(tài)
- 添加mouseMove事件,創(chuàng)一個(gè)和被拖動元素一模一樣的的浮層,將它的定位設(shè)置
它的定位為絕對定位,并且隨著鼠標(biāo)的坐標(biāo)更新。
- 使用HTML的Drag特性
- 文檔地址:拖拽操作
- 瀏覽器的默認(rèn)拖拽行為:支持圖象,鏈接和選擇的文本
- 其他元素默認(rèn)情況是不可拖拽的。
如果想可以拖拽可以設(shè)置為draggable = true
- 使用dragstart事件監(jiān)控拖動開始,并設(shè)置對應(yīng)的屬性
LayerList組件中添加draggable屬性
// LayerList.vue
<li
class="ant-list-item"
v-for="item in list" :key="item.id"
:class="{ active: item.id === selectedId }"
@click="handleClick(item.id)"
draggable="true"
></li>
這樣就可以有效果了:當(dāng)拖動對應(yīng)條目的時(shí)候,它會自動生成半透明的條目,并且跟隨鼠標(biāo)的移動。
接下來就開始使用dragstart事件監(jiān)控拖動開始,并設(shè)置對應(yīng)的屬性
- 給被拖動元素添加特定的狀態(tài):使用一系列的事件來監(jiān)控拖動的進(jìn)度,使用
dragStart
開始拖動操作
// LayerList.vue
// html部分
<li
class="ant-list-item"
v-for="item in list" :key="item.id"
:class="{ active: item.id === selectedId, ghost: dragData.currentDragging === item.id}"
@click="handleClick(item.id)"
@dragstart="onDragStart($event, item.id)"
draggable="true"
></li>
// js部分(setup)
const dragData = reactive({
currentDragging: ''
})
const onDragStart = (e: DragEvent, id: string ) => {
dragData.currentDragging = id;
}
// css部分
.ant-list-item.ghost {
opacity: 0.5;
}
完成出來的效果:
接下來就是在鼠標(biāo)松開的時(shí)候,特定的狀態(tài)消失:使用drop
事件:
<ul :list="list" class="ant-list-items ant-list-border" @drop="onDrop"></ul>
const onDrop = (e: DragEvent ) => {
dragData.currentDragging = '';
}
但是這樣做發(fā)現(xiàn)不起作用,后來發(fā)現(xiàn)是onDrog
事件并沒有觸發(fā),原因:dragenter 或 dragover 事件的監(jiān)聽程序用于表示有效的放置目標(biāo),也就是被拖拽項(xiàng)目可能放置的地方。網(wǎng)頁或應(yīng)用程序的大多數(shù)區(qū)域都不是放置數(shù)據(jù)的有效位置。因此,這些事件的默認(rèn)處理是不允許放置。
指定放置對象
因?yàn)榫W(wǎng)頁大部分區(qū)域不是有效的放置位置,這些事件的默認(rèn)處理都是不允許放置,所以這個(gè)行為并不會被觸發(fā)。如果你想要允許放置,你必須取消 dragenter 和 dragover 事件來阻止默認(rèn)的處理。你可以在屬性定義的事件監(jiān)聽程序返回 false,或者調(diào)用事件的 preventDefault() 方法來實(shí)現(xiàn)這一點(diǎn)。在一個(gè)獨(dú)立腳本中的定義的函數(shù)里,可能后者更可行。
最終添加阻止默認(rèn)行為事件:
<ul :list="list" class="ant-list-items ant-list-border" @drop="onDrop" @dragover="onDragOver">
const onDragOver = (e: DragEvent) => {
e.preventDefault()
}
處理松開鼠標(biāo)時(shí)進(jìn)行排序
-
修改dragData 添加一個(gè)當(dāng)前索引的屬性
const dragData = reactive({ currentDragging: '', currentIndex: -1, });
-
@dragstart=“onDragStart($event, item.id, index)” 方法中多添加一個(gè)index參數(shù)
const onDragStart = (e: DragEvent, id: string, index: number) => { dragData.currentDragging = id; dragData.currentIndex = index; };
有了開始拖動的index之后,我們要知道drop的時(shí)候新的index,我們怎么在onDrop
方法中拿到新的index呢?因?yàn)樵?code>onDrop中我們的參數(shù)是event
,使用event.target
可以拿到dom元素,把最新的index放到dom元素上面就可以了,使用:HTMLElement.dataset
3. 使用 HTMLElement.dataset
拿到最新的索引
HTMLElement.dataset屬性允許無論是在讀取模式和寫入模式下訪問在HTML 或 DOM中元素上設(shè)置的
所有自定義數(shù)據(jù)屬性(data-*)集
它是一個(gè)DOMString的映射,每個(gè)自定義數(shù)據(jù)屬性的一個(gè)條目。
請注意,dataset屬性本身可以被讀取,但不能直接寫入,相反,所有的寫入必股友是它的屬性,這反過來
表示數(shù)據(jù)屬性。
還要注意,一個(gè)HTML data-attribute 及其對應(yīng)的DOM dataset.property 不共享相同的名稱,但它
們總是相似的:
<li
class="ant-list-item"
:class="{
active: item.id === selectedId,
ghost: dragData.currentDragging === item.id,
}"
v-for="(item, index) in list"
:key="item.id"
@click="handleClick(item.id)"
@dragstart="onDragStart($event, item.id, index)"
:data-index="index"
draggable="true"
>
- 修改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
是不一定存在的,因?yàn)?code>e.target是鼠標(biāo)指向的元素,所以當(dāng)在目標(biāo)子元素上面進(jìn)行釋放的話,就會把目標(biāo)當(dāng)成子元素,比如如果釋放到的元素是鎖元素,則currentEle
就是鎖元素。所以這里需要一個(gè)方法來向上進(jìn)行檢索,找到符合條件的父元素。
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改變數(shù)組
arrayMove.mutate(props.list, dragData.currentIndex, moveIndex);
}
dragData.currentDragging = '';
};
array-move
最終實(shí)現(xiàn)的效果:
在拖動時(shí)完成排序:
const onDragEnter = (e: DragEvent, index: number) => {
// 這里的判斷是為了避免完成轉(zhuǎn)換后,觸發(fā)新的一次dragEnter事件
if (index !== dragData.currentIndex) {
console.log('enter', index, dragData.currentIndex);
arrayMove.mutate(props.list, dragData.currentIndex, index);
dragData.currentIndex = index;
end = index
}
};
這樣就可以在拖動時(shí)完成排序了,onDrop
里面就不需要進(jìn)行同樣的操作了,修改一下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 = '';
};
現(xiàn)在就完成了可拖動排序的簡單編碼,主要掌握三個(gè)階段:
- 排序開始:監(jiān)控被拖拽的元素,添加特殊狀態(tài)和UI
- 移動階段:進(jìn)入別的列表的時(shí)候完成數(shù)據(jù)的交換
- drop階段:松開按鈕的時(shí)候,將狀態(tài)恢復(fù)原狀,并且發(fā)送對應(yīng)的事件。
使用第三方庫進(jìn)行排序:
使用Vue Draggable進(jìn)行排序:
vue.draggable.next
npm i -S vuedraggable@next
將用draggable替換掉ul文章來源:http://www.zghlxwxcb.cn/news/detail-824560.html
<template>
<draggable
:list="list"
class="ant-list-items ant-list-bordered"
ghost-class="ghost"
handle=".handle"
item-key="id"
>
<template #item="{ element }">
<li
class="ant-list-item"
:class="{ active: element.id === selectedId }"
@click="handleClick(element.id)"
>
<a-tooltip :title="element.isHidden ? '顯示' : '隱藏'">
<a-button
shape="circle"
@click.stop="
handleChange(element.id, 'isHidden', !element.isHidden)
"
>
<template v-slot:icon v-if="element.isHidden"
><EyeInvisibleOutlined />
</template>
<template v-slot:icon v-else><EyeOutlined /> </template>
</a-button>
</a-tooltip>
<a-tooltip :title="element.isLocked ? '解鎖' : '鎖定'">
<a-button
shape="circle"
@click.stop="
handleChange(element.id, 'isLocked', !element.isLocked)
"
>
<template v-slot:icon v-if="element.isLocked"
><LockOutlined/>
</template>
<template v-slot:icon v-else><UnlockOutlined /> </template>
</a-button>
</a-tooltip>
<inline-edit
class="edit-area"
:value="element.layerName"
@change="
(value) => {
handleChange(element.id, 'layerName', value);
}
"
></inline-edit>
<a-tooltip title="拖動排序">
<a-button shape="circle" class="handle">
<template v-slot:icon><DragOutlined /> </template
></a-button>
</a-tooltip>
</li>
</template>
</draggable>
</template>
<script lang="ts">
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 PropType<ComponentData[]>,
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,
};
},
});
</script>
<style 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>
文章來源地址http://www.zghlxwxcb.cn/news/detail-824560.html
到了這里,關(guān)于web架構(gòu)師編輯器內(nèi)容-圖層拖動排序功能的開發(fā)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!