[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 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.disabled) return this.mouseup_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 => this.isMouseInTraget(target, e))
                        break;
                    case 'crash'://碰撞接触
                    default:
                        r = targets.filter(target => this.isCrash(target, this.cloneDrag))
                }
            }
            return r;// 获取被碰撞接触的内容
        },
        //鼠标在元素内部
        isMouseInTraget(targetDom, mousePoint) {
            if (!mousePoint) return false;
            let tr = targetDom.getBoundingClientRect();
            let t = { x1: tr.x, x2: tr.x + tr.width, y1: tr.y, y2: tr.y + tr.height };//目标对象的四角顶点坐标
            let isIn =
                mousePoint.clientX >= t.x1 && mousePoint.clientX <= t.x2 &&
                mousePoint.clientY >= t.y1 && mousePoint.clientY <= t.y2;
            return isIn;
        },
        //碰撞检测
        isCrash(targetDom, moveDom) {
            /*
            targetDom:目标对象(即将要被碰撞的元素)
            moveDom:移动的对象(可能是拖拽移动,也可能是其他原因导致其移动)
            */
            if (targetDom === moveDom) return false;//如果目标对象和移动对象是同一个,返回未接触
            let tr = targetDom.getBoundingClientRect(), mr = moveDom.getBoundingClientRect();
            let t = { x1: tr.x, x2: tr.x + tr.width, y1: tr.y, y2: tr.y + tr.height };//目标对象的四角顶点坐标
            let m = { x1: mr.x, x2: mr.x + mr.width, y1: mr.y, y2: mr.y + mr.height };//移动对象的四角顶点坐标
            let a = { w: Math.min(t.x2, m.x2) - Math.max(t.x1, m.x1), h: Math.min(t.y2, m.y2) - Math.max(t.y1, m.y1) };//计算相交部分的宽度和高度
            let area = (a.w > 0 ? a.w : 0) * (a.h > 0 ? a.h : 0);//计算相交部分的面积
            return area ? true : false;//面积>0,即碰撞(这里可以根据业务需求,改成相交部分面积>具体的值才作为碰撞判断)
        },
    }
};
</script>
    
<style lang="scss" scoped>
.sgDrag {
    pointer-events: none;

    .cloneDrag {
        position: fixed;
        z-index: 999; //根据情况自己拿捏
        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>

ここでのドラッグアンドドロップのネイティブ記述方法は広く使われています[Wall Crack Recommendation] [Native Basic Edition] js がネイティブでドラッグアンドドロップ効果を実現します. div のカーソルはグラブとグラブを使用することを忘れないように注意してください, これはまだ古い方法です. このプロパティのデフォルト値は false です. 次のメソッドの欠点は、ドラッグが放棄されたときにクリック イベントがトリガーされることです。通常、ドラッグされた要素に他のクリック イベントがある場合、繰り返しトリガーされますが、これは多くの場合ビジネス要件ではありません。このコードには何の利点もないようです。......_cursor: https://blog.csdn.net/qq_37860634/article/details/106368048を取得します

sgDrag 2.0 バージョンはこちら↓ 【sgDrag_v2】[独自の研究開発] クローン要素を生成する html2canvas のサイクルが固定されていないため、ドラッグ イベントの監視には、dragstart、drag、dragend が使用されますhttps://blog.csdn.net/qq_37860634/article/details/131676350

おすすめ

転載: blog.csdn.net/qq_37860634/article/details/131645686