1. 程式人生 > 程式設計 >Vue 可拖拽元件Vue Smooth DnD的使用詳解

Vue 可拖拽元件Vue Smooth DnD的使用詳解

目錄
  • 簡介和 Demo 展示
  • API: Container
    • 屬性
    • 生命週期
    • 回撥
    • 事件
  • API: Draggable
    • 實戰

簡介和 Demo 展示

最近需要有個拖拽列表的需求,發現一個簡單好用的 可拖拽元件。安利一下~

Vue Smooth DnD 是一個快速、輕量級的拖放、可排序的 Vue. 庫,封裝了 smooth-dnd 庫。

Vue Smooth DnD 主要包含了兩個元件,ContainerDraggableContainer 包含可拖動的元素或元件,它的每一個子元素都應該被 Draggable 包裹。每一個要被設定為可拖動的元素都需要被 Draggable 包裹。

安裝: npm i vue-smooth-dnd

一個簡單的 Demo ,展示元件的基礎用法,實現了可以拖拽的列表。

<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,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,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,.125); margin-bottom: 2px; margin-top: 2px; cursor: default; user-select: none; } </style>

效果

Vue 可拖拽元件Vue Smooth DnD的使用詳解

API: Container

屬性

http://www.cppcns.com
屬性 型別 預設值 描述
:orientation string vertical 容器的方向,可以為horizontal或vertical
:behaviour string move 描述被拖動的元素被移動或複製到目標容器。 可以為move或copy或drop-zone或contain。move可以在容器間互相移動,copy是可以將元素複製到其他容器,但本容器內元素不可變,drop-zone可以在容器間移動,但是容器內元素的順序是固定的。contain只能在容器內移動。
:tag string,NodeDescription div 容器的元素標籤,預設是div,可以是字串如tag="table"也可以是包含value和props屬性的物件:tag="{value: 'table',props: {class: 'my-table'}}"
:group-name string undefined 可拖動元素可以在具有相同組名的容器之間移動。如果未設定組名容器將不接受來自外部的元素。 這種行為可以被 shouldAcceptDrop 函式覆蓋。 見下文。
:lock-axis string undefined 鎖定拖動的移動軸。可用值x,y或undefined。
:drag-handle-selector string undefined 用於指定可以開啟拖拽的 選擇器,如果不指定的話則元素內部任意位置都可抓取。
:non-drag-area-selector string undefined 禁止拖動的 CSS 選擇器,優先於dragHandleSelector.
:drag-begin-delay number 0(觸控裝置為200) 單位毫秒。表示點選元素持續多久後可以開始拖動。在此之前移動游標超過 5px 將取消拖動。
:animation-duration number 250 單位毫秒。表示放置元素和重新排序的動畫持續時間。
:auto-scroll-enabled boolean true 如果拖動專案接近邊界,第一個可滾動父項將自動滾動。(這個屬性沒看懂= =)
:drag-class string undefined 元素被拖動中的新增的類(不會影響拖拽結束後元素的顯示)。
:drop-class string undefined 從拖拽元素被放置到被新增到頁面過程中新增的類。
:remove-on-drop-out boolean undefined 如果設定為true,在被拖拽元素沒有被放置到任何相關容器時,使用元素索引作為removedIndex呼叫onDrop()
:drop-placeholder boolean,object undefined 佔位符的選項。包含className,animationDuration,showOnTop

關於 drag-classdrop-classdrop-placeholder.className 的效果演示

<Container # 省略其它屬性...
        :animation-duration="1000" # 放置元素後動畫延時
        drag-class="card-ghost"         
        drop-class="card-ghost-drop"
        :drop-placeholder="{
            className: 'drop-preview',# 佔位符的樣式
            animationDuration: '1000',# 佔位符的動畫延遲
            showOnTop: true            # 是否在其它元素的上面顯示 設定為false會被其他的拖拽元素覆蓋
        }"
>
    <!-- 一些可拖拽元素 -->
    <Draggable>....</Draggable>
</Container>

類對應樣式

.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;
}

實際效果(我這優秀的配色啊)

Vue 可拖拽元件Vue Smooth DnD的使用詳解

生命週期

一次拖動的生命週期通過一系列回撥和事件進行描述和控制,下面以包含 3 個容器的示例為例進行說明
(直接複製了文件沒有翻譯,API 詳細解釋可以看後面介紹。):

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

請注意,應在每次 drag-start 之前和每次 drag-end 之前觸發 should-accept-drop,但為了清晰起見,此處已省略。

其中 dragResult 引數的格式:

dragResult: {
    payload,# 負載 可以理解為用來記錄被拖動的物件
    isSource,# 是否是被拖動的容器本身
    willAcceptDrop,# 是否可以被放置
}

其中 dropResult 引數的格式:

dropResult: {
    addedIndex,# 被放置的新新增元素的下標,沒有則為 null
    removedIndex,# 將被移除的元素下標,沒有則為 null
    payload,# 拖動的元素物件,可通過 getChildPayload 指定
    droppedElement,# 放置的 DOM 元素
}

回撥

回撥在使用者互動之前和期間提供了額外的邏輯和檢查。

  • get-child-payload(index) 自定義傳給 onDrop()payload 物件。
  • should-accept-drop(sourceContainerOptions,payload) 用來確定容器是否可被放置,會覆蓋 group-name 屬性。
  • should-animate-drop(sourceContainerOptions,payload) 返回 false 則阻止放置動畫。
  • get-ghost-parent() 返回幽靈元素(拖動時顯示的元素)應該新增到的元素,預設是父元素,某些情況定位會出現問題,則可以選擇自定義,如返回 document.body

事件

  • @drag-start 在拖動開始時由所有容器發出的事件。引數 dragResult
  • @drag-end 所有容器在拖動結束時呼叫的函式。 在 @drop 事件之前呼叫。引數 dragResult
  • @drag-enter 每當拖動的專案在拖動時進入其邊界時,相關容器要發出的事件。
  • @drag-leave 每當拖動的專案在拖動時離開其邊界時,相關容器要發出的事件。
  • @drop-ready 當容器中可能放置位置的索引發生變化時,被拖動的容器將呼叫的函式。基本上,每次容器中的可拖動物件滑動以開啟拖動專案的空間時都會呼叫它。引數 dropResult
  • @drop 放置結束時所有相關容器會發出的事件(放置動畫結束後)。源容器和任何可以接受放置的容器都被認為是相關的。引數 dropResult

API: Draggable

tag

同容器的 tag 指定可拖拽元素的 DOM 元素標籤。

實戰

實現一個簡單的團隊協作工作管理員。

<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">&#x2630;</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,payload)
        if (removedIndex === null && addedIndex === null) return arr

        const result = [...arr]
        let itemToAdd = payload

        if (removedIndex !== null) {
            itemToAdd = result.splice(removedIndex,itemToAdd)
        }

        return result
    }

    const taskList = [
        {
            name: '首頁',priority: 'P1',status: '待開發',id: 1,},{
            name: '流程圖開發',priority: 'P3',status: '待評審',id: 2,{
            name: '統計圖展示',priority: 'P0',status: '開發中',id: 3,{
            name: '檔案管理',id: 4,}
    ]

    const statusList = ['待評審','待開發','開發中','已完成']

    const taskColumnList = statusList.map((status,index) => {
        return {
            name: status,list: taskList.filter(item => item.status === status),id: index
        }
    })

    const priorityMap = {
        'P0': {
            label: '最高優',color: '#ff5454','P1': {
            label: '高優',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,payload } = dropResult
                if (removedIndex !== null || addedIndex !== null) {
                    const column = taskColumnList.find(p => p.id === columnId)
                    if (addedIndex !== null && payload) { // 更新任務狀態
                        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,.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.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>

效果

Vue 可拖拽元件Vue Smooth DnD的使用詳解

到此這篇關於Vue 可拖拽元件Vue Smooth DnD的使用詳解的文章就介紹到這了,更多相關Vue 可拖拽元件內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!