Vue 可拖拽组件Vue Smooth DnD的使用详解

目录
  • 简介和 Demo 展示
  • API: Container
    • 属性
    • 生命周期
    • 回调
    • 事件
  • API: Draggable
    • 实战

简介和 Demo 展示

最近需要有个拖拽列表的需求,发现一个简单好用的 Vue 可拖拽组件。安利一下~

Vue Smooth DnD 是一个快速、轻量级的拖放、可排序的 Vue.js 库,封装了 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, 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

属性

属性 类型 默认值 描述
: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 用于指定可以开启拖拽的 CSS 选择器,如果不指定的话则元素内部任意位置都可抓取。
: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;
}

实际效果(我这优秀的配色啊)

生命周期

一次拖动的生命周期通过一系列回调和事件进行描述和控制,下面以包含 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">☰</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: '待开发',
            id: 1,
        },
        {
            name: '流程图开发',
            priority: 'P3',
            status: '待评审',
            id: 2,
        },
        {
            name: '统计图展示',
            priority: 'P0',
            status: '开发中',
            id: 3,
        },
        {
            name: '文件管理',
            priority: 'P1',
            status: '开发中',
            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, addedIndex, 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,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>

效果

到此这篇关于Vue 可拖拽组件Vue Smooth DnD的使用详解的文章就介绍到这了,更多相关Vue 可拖拽组件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue实现element-ui对话框可拖拽功能

    element-ui对话框可拖拽及边界处理 应业务需求,需要实现对话框可拖拽问题,应element-ui没有提供官方支持,于是便参考大神的文章,得出了适合业务需要的解决方案.很多大神给出的代码是没有解决边界问题的,但是不解决边界问题存在一个bug,拖到不可视区域后边再也拖不回来了,不信你们可以试试. 在实现的功能的情况下,封装成了js文件,然后再main.js中引入后可全局使用. 还是上代码吧 功能实现代码directives.js代码如下: import Vue from 'vue'; //

  • vue实现可拖拽的dialog弹框

    本文主要介绍了vue实现可拖拽的dialog弹框,分享给大家,具体如下: element的dialog弹框在项目中挺常用的.但有时候嵌套的话会遮住,体验不好.拖拽形式的弹框会提高用户体验 借助基于 Sortable.js 的 Vue 拖拽组件vuedraggable 安装 npm install vuedraggable --save 在公共组件中新建个js文件,搭配vue自定义指令来实现拖拽的效果 import Vue from 'vue'; // v-dialogDrag: 弹窗拖拽属性 V

  • vue悬浮可拖拽悬浮按钮的实例代码

    前言 vue开发手机端悬浮按钮实现,可以拖拽,滚动的时候收到里边,不影响视线 github地址 使用,基于vue-cli3.0+webpack 4+vant ui + sass+ rem适配方案+axios封装,构建手机端模板脚手架 vue-h5-template 后续将发布各种商城组件组件,让商城简单高效开发 线上体验 使用 将 src/components文件夹下的s-icons组件放到你的组件目录下 使用组件 // template <template> <div> <

  • vue draggable resizable 实现可拖拽缩放的组件功能

    虽然之前适配过旧版组件,但是因为2.0版本原作者对代码进行了重构,原来修改的代码照搬是不可能的了. 所以也就一直没有将 冲突检测 以及 吸附对齐 功能适配到2.0版本,最近正好有时间就适配一下. 新增特征 冲突检测 吸附对齐 默认样式优化 功能预览 项目地址 github.com/gorkys/vue-- 如果喜欢该项目,欢迎 Star 新增Props isConflictCheck Type: Boolean Required: false Default: false 定义组件是否开启冲突检

  • Vue 可拖拽组件Vue Smooth DnD的使用详解

    目录 简介和 Demo 展示 API: Container 属性 生命周期 回调 事件 API: Draggable 实战 简介和 Demo 展示 最近需要有个拖拽列表的需求,发现一个简单好用的 Vue 可拖拽组件.安利一下~ Vue Smooth DnD 是一个快速.轻量级的拖放.可排序的 Vue.js 库,封装了 smooth-dnd 库. Vue Smooth DnD 主要包含了两个组件,Container 和 Draggable,Container 包含可拖动的元素或组件,它的每一个子元

  • WPF中鼠标/键盘/拖拽事件以及用行为封装事件详解

    目录 鼠标事件 键盘输入事件 拖拽事件 用行为封装事件 用事件来实现 用行为来封装 本文主要介绍了WPF中常用的鼠标事件.键盘事件以及注意事项,同时使用一个案例讲解了拓展事件.除此之外,本文还讲述如何用行为(Behavior)来封装事件. Windows中的事件通过消息机制来完成,也就是Windows系统来捕获用户输入(如鼠标点击.键盘输入),然后Windows发送一个消息给应用程序,应用程序进行具体的处理.在Winform中,窗体中每个控件都是有独立的句柄,也就是每个控件都可以收到Window

  • Vue拖拽组件开发实例详解

    为什么选择Vue? 主要原因:对于前端开发来说,兼容性是我们必须要考虑的问题之一.我们的项目不需要兼容低版本浏览器.项目本身也是一个数据驱动型的.加之,Vue本身具有以下主要特性: •使用虚拟DOM: •轻量级框架: •高效的数据绑定: •灵活的组件系统: •完整的开发生态链. 这就是我们为什么选择Vue框架的一些原因. 为什么要封装成一个Vue组件? 主要目的是可提高代码的复用性和可维护性. •复用性:组件化后,一些样式和逻辑均通过配置参数的方式去差异化体现,所以参数的可配置性提高了组件的复用

  • Vue拖拽组件列表实现动态页面配置功能

    需求描述 最近在做一个后台系统,有一个功能产品需求是页面分为左右两部分,通过右边的组件列表来动态配置左边的页面视图,并且左边由组件拼装起来的视图,可以实现上下拖拽改变顺序,也可以删除. 根据这个需求我做了下面这个demo. 功能分解 右边的组件列表,可以通过拖拽克隆到左边,拖拽结束后右边组件列表数量不会减少 左边的组件可以展开或收起 左边的组件可以上下拖拽,删除,但不可向右边拖拽 左边组件拖拽过程中不改变其展开和收起状态 demo截图 代码地址 vue-draggable-list 代码预览 <

  • vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序

    vue拖拽克隆clone组件API, vue.draggable实现盒子之间相互拖拽排序克隆(网上资源整理的文档) 效果图: -------------------------------------------------------------------------------- 首先需要安装vuedraggable依赖包: npm install vuedraggable --save 因为拖拽组件依赖sortablejs ,如果项目没有安装sortablejs ,可能需要安装一下 np

  • vue拖拽组件使用方法详解

    前言 pc端开发需要拖拽组件完成列表的顺序交换,一般移动端的UI组件会包含,但是我在用的iview并没有此功能的组件,于是手写一个,实现起来很简单.效果图如下: 可以拖拽完成新排序,点击某一项可以触发相关事件. 关于拖拽 drag & drop 拖放(Drag 和 drop)是 HTML5 标准的组成部分. 拖拽对象 dataTransfer对象,只能在拖放事件的事件处理程序中访问.重要属性: effectAllowed ( none | copy | copyLink | copyMove |

  • vue 实现拖拽动态生成组件的需求

    产品需求 开完产品需求会议,遇到了一个需求,首先页面分成两栏布局,左侧展示数据组件,支持拖拽排序,点击按钮清除组件.右侧支持将组件的缩略图拖拽至左侧生成一个新的组件. 思路 对于动态生成组件来说每一次都要是生成全新的一个组件,那么就可以把 组件放进函数当中 return.在JSX中调用函数,每次调用函数都会返回一个全新的组件.这对React来说非常简单,但是对于Vue来说,直接将组件返回是不可能的.尽管这个 return 写法不适合Vue,但是我们不可否认,思路是非常正确的,所以我们应该考虑一个

  • Vue实现可拖拽组件的方法

    本文为大家分享了Vue实现可拖拽.拖拽组件,供大家参考,具体内容如下 描述: 组件仅封装拖拽功能,内容通过#header.#default.#footer插槽 自定义 效果: 代码: <template> <div ref="wrapper" class="drag-bar-wrapper" > <div ref="header" class="drag-bar-header" > <

  • vue拖拽组件vuedraggable使用说明详解

    vue拖拽组件vuedraggable的使用说明,供大家参考,具体内容如下 需了解H5的draggable属性,通过下面的代码注释,可了解 <!DOCTYPE html>   <html lang="en">   <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-widt

  • Vue.Draggable拖拽功能的配置使用方法

    本文实例为大家分享了Vue.Draggable拖拽功能的具体代码,供大家参考,具体内容如下 使用cmd命令在项目根目录下下载安装Vue.Draggable npm install vuedraggable 在组件中需要使用的引入 import draggable from 'vuedraggable' 注册组件 components:{ draggable } vue的template代码如 <draggable v-model="itemlis" //开始移动方法 :move=

随机推荐