【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


相关文章
|
2天前
|
资源调度 JavaScript 前端开发
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
|
2天前
|
JavaScript
【vue】深入探讨vue中组件间多种传值方式
【vue】深入探讨vue中组件间多种传值方式
【vue】深入探讨vue中组件间多种传值方式
|
2天前
|
JavaScript 前端开发
vue组件化开发流程梳理,拿来即用
vue组件化开发流程梳理,拿来即用
|
2天前
|
移动开发 JavaScript 前端开发
Vue Router的介绍与引入
Vue Router的介绍与引入
|
JavaScript
Vue自定义组件事件传递:EventBus部分
组件化应用构建是Vue的特点之一,因此我们在Vue的实际开发过程中会经常需要封装自定义组件,以提高开发的效率。 而组件在大部分情况下并不会孤立的存在,它必然会与父组件和兄弟组件产生数据的交互。所以在这里为大家总结两种组件数据交互的方式:EventBus和利用Vuex框架进行状态管理。
2179 0
|
6天前
|
JavaScript
【vue】如何跳转路由到指定页面位置
【vue】如何跳转路由到指定页面位置
10 0
|
6天前
|
JavaScript 前端开发
【vue】iview如何把input输入框和点击输入框之后的边框去掉
【vue】iview如何把input输入框和点击输入框之后的边框去掉
13 0
|
6天前
|
JavaScript UED
【vue】iview组件 DatePicker 日期选择器如何显示默认当前日期
【vue】iview组件 DatePicker 日期选择器如何显示默认当前日期
10 1
|
2天前
|
JavaScript 应用服务中间件 nginx
vue中404解决方法
vue中404解决方法
|
2天前
|
JavaScript
Vue 中如何模块化使用 Vuex
Vue 中如何模块化使用 Vuex