Vue 可拖拽組件Vue Smooth DnD的使用詳解
簡(jiǎn)介和 Demo 展示
最近需要有個(gè)拖拽列表的需求,發(fā)現(xiàn)一個(gè)簡(jiǎn)單好用的 Vue 可拖拽組件。安利一下~
Vue Smooth DnD 是一個(gè)快速、輕量級(jí)的拖放、可排序的 Vue.js 庫,封裝了 smooth-dnd 庫。
Vue Smooth DnD 主要包含了兩個(gè)組件,Container
和 Draggable
,Container
包含可拖動(dòng)的元素或組件,它的每一個(gè)子元素都應(yīng)該被 Draggable
包裹。每一個(gè)要被設(shè)置為可拖動(dòng)的元素都需要被 Draggable
包裹。
安裝: npm i vue-smooth-dnd
一個(gè)簡(jiǎn)單的 Demo ,展示組件的基礎(chǔ)用法,實(shí)現(xiàn)了可以拖拽的列表。
<template> <div> <div class="simple-page"> <Container @drop="onDrop"> <Draggable v-for="item in items" :key="item.id"> <div class="draggable-item"> {{item.data}} </div> </Draggable> </Container> </div> </div> </template> <script> import { Container, Draggable } from "vue-smooth-dnd"; const applyDrag = (arr, dragResult) => { const { removedIndex, addedIndex, payload } = dragResult console.log(removedIndex, addedIndex, payload) if (removedIndex === null && addedIndex === null) return arr const result = [...arr] let itemToAdd = payload if (removedIndex !== null) { itemToAdd = result.splice(removedIndex, 1)[0] } if (addedIndex !== null) { result.splice(addedIndex, 0, itemToAdd) } return result } const generateItems = (count, creator) => { const result = [] for (let i = 0; i < count; i++) { result.push(creator(i)) } return result } export default { name: "Simple", components: { Container, Draggable }, data() { return { items: generateItems(50, i => ({ id: i, data: "Draggable " + i })) }; }, methods: { onDrop(dropResult) { this.items = applyDrag(this.items, dropResult); } } }; </script> <style> .draggable-item { height: 50px; line-height: 50px; text-align: center; display: block; background-color: #fff; outline: 0; border: 1px solid rgba(0, 0, 0, .125); margin-bottom: 2px; margin-top: 2px; cursor: default; user-select: none; } </style>
效果
API: Container
屬性
屬性 | 類型 | 默認(rèn)值 | 描述 |
---|---|---|---|
:orientation | string | vertical | 容器的方向,可以為 horizontal 或 vertical |
:behaviour | string | move | 描述被拖動(dòng)的元素被移動(dòng)或復(fù)制到目標(biāo)容器。 可以為 move 或 copy 或 drop-zone 或 contain 。move 可以在容器間互相移動(dòng),copy 是可以將元素復(fù)制到其他容器,但本容器內(nèi)元素不可變,drop-zone 可以在容器間移動(dòng),但是容器內(nèi)元素的順序是固定的。contain 只能在容器內(nèi)移動(dòng)。 |
:tag | string, NodeDescription | div | 容器的元素標(biāo)簽,默認(rèn)是 div ,可以是字符串如 tag="table" 也可以是包含 value和 props 屬性的對(duì)象 :tag="{value: 'table', props: {class: 'my-table'}}" |
:group-name | string | undefined | 可拖動(dòng)元素可以在具有相同組名的容器之間移動(dòng)。如果未設(shè)置組名容器將不接受來自外部的元素。 這種行為可以被 shouldAcceptDrop 函數(shù)覆蓋。 見下文。 |
:lock-axis | string | undefined | 鎖定拖動(dòng)的移動(dòng)軸??捎弥?nbsp;x, y 或 undefined。 |
:drag-handle-selector | string | undefined | 用于指定可以開啟拖拽的 CSS 選擇器,如果不指定的話則元素內(nèi)部任意位置都可抓取。 |
:non-drag-area-selector | string | undefined | 禁止拖動(dòng)的 CSS 選擇器,優(yōu)先于 dragHandleSelector. |
:drag-begin-delay | number | 0(觸控設(shè)備為 200) | 單位毫秒。表示點(diǎn)擊元素持續(xù)多久后可以開始拖動(dòng)。在此之前移動(dòng)光標(biāo)超過 5px 將取消拖動(dòng)。 |
:animation-duration | number | 250 | 單位毫秒。表示放置元素和重新排序的動(dòng)畫持續(xù)時(shí)間。 |
:auto-scroll-enabled | boolean | true | 如果拖動(dòng)項(xiàng)目接近邊界,第一個(gè)可滾動(dòng)父項(xiàng)將自動(dòng)滾動(dòng)。(這個(gè)屬性沒看懂= =) |
:drag-class | string | undefined | 元素被拖動(dòng)中的添加的類(不會(huì)影響拖拽結(jié)束后元素的顯示)。 |
:drop-class | string | undefined | 從拖拽元素被放置到被添加到頁面過程中添加的類。 |
:remove-on-drop-out | boolean | undefined | 如果設(shè)置為 true,在被拖拽元素沒有被放置到任何相關(guān)容器時(shí),使用元素索引作為 removedIndex 調(diào)用 onDrop() |
:drop-placeholder | boolean,object | undefined | 占位符的選項(xiàng)。包含 className, animationDuration, showOnTop |
關(guān)于 drag-class
,drop-class
和 drop-placeholder.className
的效果演示
<Container # 省略其它屬性... :animation-duration="1000" # 放置元素后動(dòng)畫延時(shí) drag-class="card-ghost" drop-class="card-ghost-drop" :drop-placeholder="{ className: 'drop-preview', # 占位符的樣式 animationDuration: '1000', # 占位符的動(dòng)畫延遲 showOnTop: true # 是否在其它元素的上面顯示 設(shè)置為false會(huì)被其他的拖拽元素覆蓋 }" > <!-- 一些可拖拽元素 --> <Draggable>....</Draggable> </Container>
類對(duì)應(yīng)樣式
.card-ghost { transition: transform 0.18s ease; transform: rotateZ(35deg); background: red !important; } .card-ghost-drop { transition: transform 1s cubic-bezier(0,1.43,.62,1.56); transform: rotateZ(0deg); background: green !important; } .drop-preview { border: 1px dashed #abc; margin: 5px; background: yellow !important; }
實(shí)際效果(我這優(yōu)秀的配色?。?/p>
生命周期
一次拖動(dòng)的生命周期通過一系列回調(diào)和事件進(jìn)行描述和控制,下面以包含 3 個(gè)容器的示例為例進(jìn)行說明
(直接復(fù)制了文檔沒有翻譯,API 詳細(xì)解釋可以看后面介紹。):
Mouse Calls Callback / Event Parameters Notes down o Initial click move o Initial drag | | get-child-payload() index Function should return payload | | 3 x should-accept-drop() srcOptions, payload Fired for all containers | | 3 x drag-start dragResult Fired for all containers | | drag-enter v move o Drag over containers | | n x drag-leave Fired as draggable leaves container | n x drag-enter Fired as draggable enters container v up o Finish drag should-animate-drop() srcOptions, payload Fires once for dropped container 3 x drag-end dragResult Fired for all containers n x drop dropResult Fired only for droppable containers
請(qǐng)注意,應(yīng)在每次 drag-start
之前和每次 drag-end
之前觸發(fā) should-accept-drop
,但為了清晰起見,此處已省略。
其中 dragResult
參數(shù)的格式:
dragResult: { payload, # 負(fù)載 可以理解為用來記錄被拖動(dòng)的對(duì)象 isSource, # 是否是被拖動(dòng)的容器本身 willAcceptDrop, # 是否可以被放置 }
其中 dropResult
參數(shù)的格式:
dropResult: { addedIndex, # 被放置的新添加元素的下標(biāo),沒有則為 null removedIndex, # 將被移除的元素下標(biāo),沒有則為 null payload, # 拖動(dòng)的元素對(duì)象,可通過 getChildPayload 指定 droppedElement, # 放置的 DOM 元素 }
回調(diào)
回調(diào)在用戶交互之前和期間提供了額外的邏輯和檢查。
get-child-payload(index)
自定義傳給onDrop()
的payload
對(duì)象。should-accept-drop(sourceContainerOptions, payload)
用來確定容器是否可被放置,會(huì)覆蓋group-name
屬性。should-animate-drop(sourceContainerOptions, payload)
返回false
則阻止放置動(dòng)畫。get-ghost-parent()
返回幽靈元素(拖動(dòng)時(shí)顯示的元素)應(yīng)該添加到的元素,默認(rèn)是父元素,某些情況定位會(huì)出現(xiàn)問題,則可以選擇自定義,如返回document.body
。
事件
@drag-start
在拖動(dòng)開始時(shí)由所有容器發(fā)出的事件。參數(shù)dragResult
。@drag-end
所有容器在拖動(dòng)結(jié)束時(shí)調(diào)用的函數(shù)。 在@drop
事件之前調(diào)用。參數(shù)dragResult
。@drag-enter
每當(dāng)拖動(dòng)的項(xiàng)目在拖動(dòng)時(shí)進(jìn)入其邊界時(shí),相關(guān)容器要發(fā)出的事件。@drag-leave
每當(dāng)拖動(dòng)的項(xiàng)目在拖動(dòng)時(shí)離開其邊界時(shí),相關(guān)容器要發(fā)出的事件。@drop-ready
當(dāng)容器中可能放置位置的索引發(fā)生變化時(shí),被拖動(dòng)的容器將調(diào)用的函數(shù)。基本上,每次容器中的可拖動(dòng)對(duì)象滑動(dòng)以打開拖動(dòng)項(xiàng)目的空間時(shí)都會(huì)調(diào)用它。參數(shù)dropResult
。@drop
放置結(jié)束時(shí)所有相關(guān)容器會(huì)發(fā)出的事件(放置動(dòng)畫結(jié)束后)。源容器和任何可以接受放置的容器都被認(rèn)為是相關(guān)的。參數(shù)dropResult
。
API: Draggable
tag
同容器的 tag
指定可拖拽元素的 DOM 元素標(biāo)簽。
實(shí)戰(zhàn)
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的團(tuán)隊(duì)協(xié)作任務(wù)管理器。
<template> <div class="card-scene"> <Container orientation="horizontal" @drop="onColumnDrop($event)" drag-handle-selector=".column-drag-handle" > <Draggable v-for="column in taskColumnList" :key="column.name"> <div class="card-container"> <div class="card-column-header"> <span class="column-drag-handle">☰</span> {{ column.name }} </div> <Container group-name="col" @drop="(e) => onCardDrop(column.id, e)" :get-child-payload="getCardPayload(column.id)" drag-class="card-ghost" drop-class="card-ghost-drop" :drop-placeholder="dropPlaceholderOptions" class="draggable-container" > <Draggable v-for="task in column.list" :key="task.id"> <div class="task-card"> <div class="task-title">{{ task.name }}</div> <div class="task-priority" :style="{ background: priorityMap[task.priority].color }"> {{ priorityMap[task.priority].label }} </div> </div> </Draggable> </Container> </div> </Draggable> </Container> </div> </template> <script> import { Container, Draggable } from "vue-smooth-dnd"; const applyDrag = (arr, dragResult) => { const { removedIndex, addedIndex, payload } = dragResult console.log(removedIndex, addedIndex, payload) if (removedIndex === null && addedIndex === null) return arr const result = [...arr] let itemToAdd = payload if (removedIndex !== null) { itemToAdd = result.splice(removedIndex, 1)[0] } if (addedIndex !== null) { result.splice(addedIndex, 0, itemToAdd) } return result } const taskList = [ { name: '首頁', priority: 'P1', status: '待開發(fā)', id: 1, }, { name: '流程圖開發(fā)', priority: 'P3', status: '待評(píng)審', id: 2, }, { name: '統(tǒng)計(jì)圖展示', priority: 'P0', status: '開發(fā)中', id: 3, }, { name: '文件管理', priority: 'P1', status: '開發(fā)中', id: 4, } ] const statusList = ['待評(píng)審', '待開發(fā)', '開發(fā)中', '已完成'] const taskColumnList = statusList.map((status, index) => { return { name: status, list: taskList.filter(item => item.status === status), id: index } }) const priorityMap = { 'P0': { label: '最高優(yōu)', color: '#ff5454', }, 'P1': { label: '高優(yōu)', color: '#ff9a00', }, 'P2': { label: '中等', color: '#ffd139', }, 'P3': { label: '較低', color: '#1ac7b5', }, } export default { name: 'Cards', components: {Container, Draggable}, data () { return { taskColumnList, priorityMap, dropPlaceholderOptions: { className: 'drop-preview', animationDuration: '150', showOnTop: true } } }, methods: { onColumnDrop (dropResult) { this.taskColumnList = applyDrag(this.taskColumnList, dropResult) }, onCardDrop (columnId, dropResult) { let { removedIndex, addedIndex, payload } = dropResult if (removedIndex !== null || addedIndex !== null) { const column = taskColumnList.find(p => p.id === columnId) if (addedIndex !== null && payload) { // 更新任務(wù)狀態(tài) dropResult.payload = { ...payload, status: column.name, } } column.list = applyDrag(column.list, dropResult) } }, getCardPayload (columnId) { return index => this.taskColumnList.find(p => p.id === columnId).list[index] }, } } </script> <style> * { margin: 0; padding: 0; font-family: 'Microsoft YaHei','PingFang SC','Helvetica Neue',Helvetica,sans-serif; line-height: 1.45; color: rgba(0,0,0,.65); } .card-scene { user-select: none; display: flex; height: 100%; margin: 20px; } .card-container { display: flex; flex-direction: column; width: 260px; min-width: 260px; border-radius: 12px; background-color: #edeff2; margin-right: 16px; height: calc(100vh - 40px); } .card-column-header { display: flex; height: 50px; margin: 0 16px; align-items: center; flex-shrink: 0; font-weight: 500; font-size: 16px; } .draggable-container { flex-grow: 1; overflow: auto; } .column-drag-handle { cursor: move; padding: 5px; } .task-card { margin: 10px; background-color: white; padding: 15px 10px; border-radius: 8px; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.12); cursor: pointer; display: flex; justify-content: space-between; } .task-title { color: #333333; font-size: 14px; } .task-priority { width: 60px; line-height: 20px; border-radius: 12px; text-align: center; color: #fff; font-size: 12px; } .card-ghost { transition: transform 0.18s ease; transform: rotateZ(5deg) } .card-ghost-drop { transition: transform 0.18s ease-in-out; transform: rotateZ(0deg) } .drop-preview { background-color: rgba(150, 150, 200, 0.1); border: 1px dashed #abc; margin: 5px; } </style>
效果
到此這篇關(guān)于Vue 可拖拽組件Vue Smooth DnD的使用詳解的文章就介紹到這了,更多相關(guān)Vue 可拖拽組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue拖拽組件 vuedraggable API options實(shí)現(xiàn)盒子之間相互拖拽排序
- Vue拖拽組件列表實(shí)現(xiàn)動(dòng)態(tài)頁面配置功能
- vue拖拽組件使用方法詳解
- Vue拖拽組件開發(fā)實(shí)例詳解
- vue 實(shí)現(xiàn)拖拽動(dòng)態(tài)生成組件的需求
- vue使用Split封裝通用拖拽滑動(dòng)分隔面板組件
- vue開發(fā)拖拽進(jìn)度條滑動(dòng)組件
- vue draggable resizable 實(shí)現(xiàn)可拖拽縮放的組件功能
- 利用Vue-draggable組件實(shí)現(xiàn)Vue項(xiàng)目中表格內(nèi)容的拖拽排序
- Vue組件Draggable實(shí)現(xiàn)拖拽功能
- Vue實(shí)現(xiàn)可拖拽組件的方法
相關(guān)文章
Element-UI結(jié)合遞歸組件實(shí)現(xiàn)后臺(tái)管理系統(tǒng)左側(cè)菜單
在Vue.js中使用遞歸組件可以方便地構(gòu)建多層級(jí)的菜單結(jié)構(gòu),遞歸組件適用于處理具有嵌套關(guān)系的數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-09-09Vue實(shí)現(xiàn)模糊查詢-Mysql數(shù)據(jù)庫數(shù)據(jù)
這篇文章主要介紹了基于Vue實(shí)現(xiàn)Mysql數(shù)據(jù)庫數(shù)據(jù)模糊查詢,下面文章我們主要實(shí)現(xiàn)的是輸入框中輸入數(shù)據(jù),根據(jù)輸入的結(jié)果模糊搜索數(shù)據(jù)庫對(duì)應(yīng)內(nèi)容,實(shí)現(xiàn)模糊查詢,感興趣的小伙伴可以進(jìn)入文章我們一起學(xué)習(xí)2021-12-12vant中的picker選擇器自定義選項(xiàng)內(nèi)容
這篇文章主要介紹了vant中的picker選擇器自定義選項(xiàng)內(nèi)容,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12vue項(xiàng)目啟動(dòng)命令個(gè)人學(xué)習(xí)記錄
最近想要學(xué)習(xí)vue,正好看到資料,如何通過命令創(chuàng)建vue項(xiàng)目的方法,就留個(gè)筆記,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目啟動(dòng)命令的相關(guān)資料,需要的朋友可以參考下2023-02-02vue3+element-plus?Dialog對(duì)話框的使用與setup?寫法的用法
這篇文章主要介紹了vue3+element-plus?Dialog對(duì)話框的使用?與?setup?寫法的使用,本文通過兩種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04