特性:
- 长按自定义时长(默认300毫秒),生成拖拽元素副本
- 拖拽显示抓取手型
- 拖拽过程实时侦听判断目标碰撞元素数组中是否有被接触的元素,并返回元素数组和相关.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>
大量用到了这里的拖拽原生写法
sgDrag 2.0版本去这里↓