【sgDrag】自定义组件:基于Vue开发支持批量声明拖拽元素、被碰撞元素,拖拽全过程监听元素碰撞检测并返回拖拽原始元素、克隆元素及其getBoundingClientRect对象和碰撞接触元素数组。

简介: 【sgDrag】自定义组件:基于Vue开发支持批量声明拖拽元素、被碰撞元素,拖拽全过程监听元素碰撞检测并返回拖拽原始元素、克隆元素及其getBoundingClientRect对象和碰撞接触元素数组。



特性

  1. 长按自定义时长(默认300毫秒),生成拖拽元素副本
  2. 拖拽显示抓取手型
  3. 拖拽过程实时侦听判断目标碰撞元素数组中是否有被接触的元素,并返回元素数组和相关.getBoundingClientRect()参数

sgDrag.vue组件源码

<template>
    <div :class="$options.name">
        <img ref="cloneDrag" class="cloneDrag" :src="src" v-if="src" :style="{ ...style, ...customStyle }"
            draggable="false" />
    </div>
</template>
<script>
import $g from "@/js/sg";
import html2canvas from 'html2canvas'; // npm install --save html2canvas
export default {
    name: 'sgDrag',
    data: () => ({
        offset: { x: 0, y: 0, },//偏移量
        style: { top: '0px', left: '0px', },
        src: null,//克隆的元素图片
        cloneDrag: null,//cloneDrag元素
        originDrag: null,//被拖拽的原始元素
        mouseLeaveOriginDrag: false,//当前鼠标已经离开原始元素
        crashDoms: [],//记录碰撞接触的元素
        timeout: null,
    }),
    props: [
        "data",//可以被拖拽的元素数组(必选)
        "targets",//需要被拖拽接触碰撞检测的元素(必选)
        "type",//屏蔽(可选值:crash[碰撞接触]|mouse[鼠标点位接触],默认值:crash)
        "disabled",//屏蔽
        "customStyle",//自定义拖拽过程样式
        "delay",//默认长按0.2秒 
    ],
    watch: {
        data: {
            handler(newValue, oldValue) {
                newValue ? this.__addDragsEvents(newValue) : this.__removeDragsEvents(oldValue);
            }, deep: true, immediate: true,
        },
        disabled: {
            handler(newValue, oldValue) {
                newValue ? this.__removeAllEvents() : this.__addAllEvents();
            }, deep: true, immediate: true,
        },
    },
    destroyed() {
        this.__removeAllEvents();
    },
    methods: {
        __addAllEvents() {
            this.__addDragsEvents(this.data);
        },
        __removeAllEvents() {
            this.__removeWindowEvents();
            this.__removeDragsEvents(this.data);
            this.mouseup_window();
        },
        __addWindowEvents() {
            this.__removeWindowEvents();
            addEventListener('mousemove', this.mousemove_window);
            addEventListener('mouseup', this.mouseup_window);
        },
        __removeWindowEvents() {
            removeEventListener('mousemove', this.mousemove_window);
            removeEventListener('mouseup', this.mouseup_window);
        },
        // 初始化需要拖拽的DIV
        __addDragsEvents(doms) {
            (doms || []).forEach(dom => {
                this.__addDraggedEvents(dom);
            });
        },
        __removeDragsEvents(doms) {
            (doms || []).forEach(dom => {
                this.__removeDraggedEvents(dom);
            });
        },
        __addDraggedEvents(dom) {
            this.__removeDraggedEvents(dom);
            dom.addEventListener('dragstart', this.dragstart);
            dom.addEventListener('mousedown', this.mousedown);
            dom.addEventListener('mouseup', this.mouseup);
            dom.addEventListener('mouseleave', this.mouseleave);
        },
        __removeDraggedEvents(dom) {
            dom.removeEventListener('dragstart', this.dragstart);
            dom.removeEventListener('mousedown', this.mousedown);
            dom.removeEventListener('mouseup', this.mouseup);
            dom.removeEventListener('mouseleave', this.mouseleave);
        },
        dragstart(e) {
            e.stopPropagation(); e.preventDefault(); return false;
        },
        mousedown(e) {
            if (e.button === 2) return this.mouseup_window(e);//点击了鼠标右键
            if (this.disabled) return this.mouseup_window(e);
            this.mouseLeaveOriginDrag = false;
            this.originDrag = e.currentTarget;
            this.timeout = setTimeout(() => {
                this.timeout = null;
                if (this.originDrag) {
                    html2canvas(this.originDrag).then(canvas => {
                        // 在还未生成副本图片的时候就离开了原始元素
                        if (this.mouseLeaveOriginDrag) {
                            this.mouseup_window(e);
                        } else {
                            //只有生成了副本图片才可以开始记录鼠标移动监听
                            this.src = canvas.toDataURL('image/jpeg', 1.0);
                            this.$nextTick(() => {
                                if (this.disabled) return this.mouseup_window(e);
                                this.cloneDrag = this.$refs.cloneDrag;
                                if (this.originDrag) {
                                    let or = this.originDrag.getBoundingClientRect();
                                    this.offset = { x: e.clientX - or.x, y: e.clientY - or.y, };
                                    this.$emit('dragStart', this.getResult(e));
                                    this.mousemove_window(e);
                                    this.__addWindowEvents();
                                }
                            });
                        }
                    });
                }
            }, typeof this.delay === 'undefined' ? 200 : this.delay);
        },
        mouseleave(e) {
            if (this.disabled) return this.mouseup_window(e);
            this.mouseLeaveOriginDrag || this.$emit('dragOut', this.getResult(e));
            this.mouseLeaveOriginDrag = true;
        },
        mouseup(e) {
            this.timeout && (clearTimeout(this.timeout), this.timeout = null);
        },
        mousemove_window(e) {
            if (this.cloneDrag) {
                this.cloneDrag.setAttribute("dragging", "true"); //拖拽过程变成虚线的样子
                let x = e.clientX - this.offset.x;
                let y = e.clientY - this.offset.y;
                this.style = { left: x + 'px', top: y + 'px', };
                this.$nextTick(() => {
                    // 当新碰撞接触的对象数组和上次碰撞接触的对象数组不同的时候执行
                    this.crashDoms = this.getCrashDoms(e);
                    this.$emit('dragging', this.getResult(e));
                });
            }
        },
        mouseup_window(e) {
            this.$emit('dragEnd', this.getResult(e));
            this.offset = null;
            this.style = null;
            this.src = null;
            this.originDrag && this.originDrag.removeAttribute('draggable');
            this.originDrag = null;
            this.mouseLeaveOriginDrag = false;
            this.crashDoms = [];
            this.__removeWindowEvents();
        },
        getResult(e) {
            return {
                $event: e,
                originDrag: this.originDrag,
                originDragRect: this.originDrag ? this.originDrag.getBoundingClientRect() : null,
                cloneDrag: this.cloneDrag,
                cloneDragRect: this.cloneDrag ? this.cloneDrag.getBoundingClientRect() : null,
                crashDoms: this.crashDoms,
            }
        },
        // 获取碰撞的DOM
        getCrashDoms(e) {
            let r = [];
            let targets = this.targets;
            targets['item'] && (targets = [].slice.call(targets));//NodeList对象
            Array.isArray(targets) || (targets = [targets]);//单个元素处理成数组
            if (targets && targets.length) {
                switch (this.type) {
                    case 'mouse'://鼠标点位接触
                        r = targets.filter(target => $g.isMouseInTarget(target, e))
                        break;
                    case 'crash'://碰撞接触
                    default:
                        r = targets.filter(target => $g.isCrash(target, this.cloneDrag))
                }
            }
            return r;// 获取被碰撞接触的内容
        },
    }
};
</script>
<style lang="scss" scoped>
.sgDrag {
    pointer-events: none;
    .cloneDrag {
        z-index: 999999; //根据情况自己拿捏
        position: fixed;
        left: 0;
        top: 0;
        pointer-events: auto;
        cursor: grab;
        &:active {
            cursor: grabbing;
            opacity: 0.618;
        }
        &[dragging] {
            cursor: grabbing;
            opacity: 0.9;
            border: 1px dashed #409EFF;
            transform: translate(-3px, -3px);
            box-shadow: 5px 10px 0 rgba(0, 0, 0, 0.05);
        }
    }
}
</style>

应用组件

<template>
    <div class="sg-body">
        <sgDrag :data="data" :targets="targets" @dragStart="dragStart" @dragging="dragging" @dragOut="dragOut"
            @dragEnd="dragEnd" :customStyle="{
                'box-color': '#409EFF',
                'box-shadow': '0 12px 22px 0 #409EFF'
            }" />
        <div class="drag-container">
            <div class="title">从此处拖拽出去</div>
            <div class="drag-div" :ref="`drag-div${i}`" v-for="(a, i) in items" :key="i" :index="i">{{ a.label }}</div>
        </div>
        <div class="drag-in-container drag-container" ref="drag-in-container">
            <div class="title">放入此处</div>
            <div class="drag-div" :ref="`drag-div${i}`" v-for="(a, i) in dragInItems" :key="i" :index="i">{{ a.label }}
            </div>
        </div>
    </div>
</template>
<script>
import sgDrag from "@/vue/components/sgDrag";
export default {
    components: { sgDrag },
    data: () => ({
        data: [],//可以被拖拽的元素数组
        targets: [],//需要被拖拽接触碰撞检测的元素
        checkboxGroupValue: [],
        items: [...Array(20)].map((v, i) => ({ label: '显示文本' + i, value: i })),
        dragInItems: [],//动态拖拽进入的元素列表
    }),
    mounted() {
        this.data = [...Array(20)].map((v, i) => this.$refs[`drag-div${i}`][0]);
        this.targets = [this.$refs['drag-in-container']];
        console.log(this.data)
    },
    methods: {
        dragStart(d) {
            // console.log(`拖拽开始`, d);
        },
        dragging(d) {
            // console.log(`拖拽中`, d);
            let crashDoms = d.crashDoms;
            this.targets.forEach(dom => {
                crashDoms.includes(dom) ? dom.setAttribute('active', true) : dom.removeAttribute('active');
            });
        },
        dragOut(d) {
            // console.log(`拖拽的时候鼠标离开元素`, d);
        },
        dragEnd(d) {
            // console.log(`拖拽结束`, d);
            this.targets.forEach(dom => dom.removeAttribute('active'));
            // 如果翻下拖拽那一刻,正好在右侧区域内
            if (d.crashDoms.includes(this.targets[0])) {
                let index = parseInt(d.originDrag.getAttribute('index'));
                this.dragInItems.push(this.items.splice(index, 1)[0]);
                console.log(this.dragInItems, this.items)
            }
        },
    }
};
</script>
<style lang="scss" scoped>
.sg-body {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    .title {
        width: 100%;
        height: 50px;
        display: flex;
        justify-content: center;
        align-items: center;
        font-weight: bold;
        font-size: 18px;
    }
    .drag-div {
        width: 100px;
        height: 50px;
        background-color: #409EFF;
        margin: 2px;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .drag-container {
        width: 300px;
        height: 500px;
        overflow-y: auto;
        background-color: #00000011;
        display: flex;
        flex-wrap: wrap;
        align-content: flex-start;
    }
    .drag-in-container {
        background-color: #409EFF11;
        box-sizing: border-box;
        border: 1px dashed transparent;
        &[active] {
            border-color: #409EFF;
            background-color: #409EFF22;
        }
    }
}
</style>

大量用到了这里的拖拽原生写法

image.png

sgDrag 2.0版本去这里↓

image.png


相关文章
|
27天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
2天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
28天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
1月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
36 1
vue学习第一章
|
1月前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
26 1
vue学习第三章
|
1月前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
35 1
vue学习第四章
|
1月前
|
JavaScript 前端开发 算法
vue学习第7章(循环)
欢迎来到瑞雨溪的博客,一名热爱JavaScript和Vue的大一学生。本文介绍了Vue中的v-for指令,包括遍历数组和对象、使用key以及数组的响应式方法等内容,并附有综合练习实例。关注我,将持续更新更多优质文章!🎉🎉🎉
24 1
vue学习第7章(循环)
|
1月前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
29 1
vue学习第九章(v-model)
|
1月前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
40 1
vue学习第十章(组件开发)
|
1月前
|
JavaScript 前端开发
vue学习第十一章(组件开发2)
欢迎来到我的博客,我是瑞雨溪,一名自学前端两年半的大一学生,专注于JavaScript与Vue。本文介绍Vue中的插槽(slot)使用方法,包括基本插槽、具名插槽及作用域插槽,帮助你在组件开发中实现内容的灵活定制。如果你觉得有帮助,请关注我,持续更新中!🎉🎉🎉
23 1
vue学习第十一章(组件开发2)
下一篇
DataWorks