前言
记得以前同事写过一个拖拽(我也写个菜鸟级别的吧),最近我也遇到这个业务,大概的整理一个最基础的demo,而不做更多的功能,方便不同业务手动扩展(就是懒,但是不能说!)
使用说明
好 好像都有备注说明了, ...
坑,总得说一下吧
1、slot="header"的头部作为鼠标拖拽部分,思考以下怎么拿到这个dom ?
1、插槽可不能绑定事件!
2、是不是想到在这个插槽外嵌套一个标签?你试试就知道了,这是个坑,不是说事件不起作用,而是如果改插槽里面内容覆盖的地方,事件也被“覆盖”,你点不到这个事件,你能点到就恐怖了!细品...
3、那解决方式?答案时肯定有撒,this.$slots.header[0].elm获取插槽dom,这时候如果你想了解更透彻,看看1.x和2.x+之间插槽舍弃了些什么。
2、什么时候为(this.$slots.header[0].elm)绑定事件,什么时候又给document绑定事件
当鼠标按下时,为this.$slots.header[0].elm绑定事件,目的获取鼠标在this.$slots.header[0].elm的坐标
(注:this.option.drapHeader = this.$slots.header[0].elm)
this.addEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
鼠标滑动时,时给document绑定事件,目的时获取鼠标在可视区域的坐标
this.addEvent(document, 'mousemove', this.mouseMove)
先上图,看看效果
来,实在点,说再多不如上代码实用
demo.vue
<template>
<div class="drag-wrap">
<draggable :width="500" :bottom="20" :right="20" :offset="[100,0,0,200]">
<template slot="header">
<div class="drag-handle">按下拖动</div>
</template>
<template slot="body">
<div class="drag-content">
这是内容
</div>
</template>
</draggable>
</div>
</template>
<script>
import Draggable from './component/Draggable'
export default {
...
components: {
Draggable
}
...
}
</script>
<style>
.drag-wrap{
.drag-handle{
height: 40px;
line-height: 40px;
background: #ccc;
cursor: all-scroll;
}
.drag-content{
height: 300px;
width: 100%;
background: #eee;
}
}
</style>
拖拽组件 Draggable.vue
<template>
<div ref="draggableWrap" :style="`width:${width}px`" class="draggable-wrap">
<slot name="header"></slot>
<slot name="body"></slot>
</div>
</template>
<script>
export default {
name: 'draggable',
props: {
// 只支持以下组合:top/left、top/right、bottom/left、bottom/right。注意top/bottom、left/right不可同时传递
top: {
type: Number,
default: null
},
bottom: {
type: Number,
default: null
},
left: {
type: Number,
default: null
},
right: {
type: Number,
default: null
},
center: { // 显示在中心
type: Boolean,
default: false
},
width: {
type: Number,
default: 500
},
isDrag: { // 是否开启拖动
type: Boolean,
default: true
},
overflow: { // 是否超出屏幕,默认否,配合offset使用
type: Boolean,
default: false
},
offset: {
type: Array,
default: () => { // 距离屏幕边距,分别代表:top/right/bottom/left(px)
return [0, 0, 0, 0]
}
}
},
data () {
return {
option: {
drapDom: null, // 拖动元素
drapHeader: null, // 拖动元素插槽的元素
dragHeight: null, // 拖动元素的高度
dragWidth: null,
winWidth: null, // 浏览器窗口宽度
winHeight: null,
mouseX: 0, // 鼠标按下时相对拖动元素的left坐标
mouseY: 0 // 鼠标按下时相对拖动元素的top坐标
}
}
},
mounted () {
this.option.drapHeader = this.$slots.header[0].elm
this.initData()
window.addEventListener('resize', this.initData)
},
destroyed () {
// window.removeEventListener('resize', this.initData)
this.removeEvent(window, 'resize', this.initData)
this.removeEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
},
methods: {
// 初始化信息
initData () {
let drapDom = this.$refs.draggableWrap
this.option.drapDom = drapDom
this.option.dragHeight = drapDom.offsetHeight || 0
this.option.dragWidth = drapDom.offsetWidth || 0
this.option.winHeight = window.innerHeight || 0
this.option.winWidth = window.innerWidth || 0
this.initPosition()
},
// 初始化定位
initPosition () {
if (!this.center) {
if (this.bottom !== null) {
this.option.drapDom.style.top = this.option.winHeight - this.option.dragHeight - this.bottom + 'px'
}
if (this.right !== null) {
this.option.drapDom.style.left = this.option.winWidth - this.option.dragWidth - this.right + 'px'
}
if (this.top !== null) {
this.option.drapDom.style.top = this.top + 'px'
}
if (this.left !== null) {
this.option.drapDom.style.left = this.left + 'px'
}
} else {
this.option.drapDom.style.top = this.option.winHeight / 2 - this.option.dragHeight / 2 + 'px'
this.option.drapDom.style.left = this.option.winWidth / 2 - this.option.dragWidth / 2 + 'px'
}
if (this.isDrag) {
this.addEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
}
},
// 鼠标按下
mouseDown (e) {
this.option.mouseX = e.offsetX
this.option.mouseY = e.offsetY
this.addEvent(document, 'mousemove', this.mouseMove)
this.addEvent(document, 'mouseup', this.mouseUp)
},
// 鼠标松开,移除事件
mouseUp (e) {
this.removeEvent(document, 'mousemove', this.mouseMove)
},
// 鼠标移动
mouseMove (e) {
let ny = e.pageY - this.option.mouseY
let nx = e.pageX - this.option.mouseX
if (this.overflow) {
this.option.drapDom.style.top = ny + 'px'
this.option.drapDom.style.left = nx + 'px'
} else {
let maxY = ny >= this.option.winHeight - this.option.dragHeight - this.offset[2] ? this.option.winHeight - this.option.dragHeight - this.offset[2] : ny
let maxX = nx >= this.option.winWidth - this.option.dragWidth - this.offset[1] ? this.option.winWidth - this.option.dragWidth - this.offset[1] : nx
this.option.drapDom.style.top = ny <= this.offset[0] ? this.offset[0] : maxY + 'px'
this.option.drapDom.style.left = nx <= this.offset[3] ? this.offset[3] : maxX + 'px'
}
},
// 绑定事件
addEvent (el, event, handler) {
if (!el) {
return
}
if (el.attachEvent) {
el.attachEvent('on' + event, handler)
} else if (el.addEventListener) {
el.addEventListener(event, handler, true)
} else {
el['on' + event] = handler
}
},
// 移除事件
removeEvent (el, event, handler) {
if (!el) {
return
}
if (el.detachEvent) {
el.detachEvent('on' + event, handler)
} else if (el.removeEventListener) {
el.removeEventListener(event, handler, true)
} else {
el['on' + event] = null
}
}
}
}
</script>
<style>
.draggable-wrap{
position: fixed;
top: 20px;
left: 20px;
}
</style>