c
分清clientY pageY screenY layerY offsetY的区别
在我们想要做出拖拽这个效果的时候,我们需要分清这几个属性的区别,这几个属性都是计算鼠标点击的偏移值,我们需要对其进行了解才可以继续实现我们的拖拽效果
- clientY 指的是距离可视页面左上角的距离
- pageY 指的是距离可视页面左上角的距离(不受页面滚动影响)
- screenY 指的是距离屏幕左上角的距离
- layerY 指的是找到它或它父级元素中最近具有定位的左上角距离
- offsetY 指的是距离它自己左上角的距离
在简单了解完这些个属性以后,有几个属性需要分清。
相同点 | 不同点 | |
---|---|---|
clientY | 距离页面左上角距离 | 受页面滚动的影响 |
pageY | 距离页面左上角的距离 | 不受页面滚动影响 |
相同点 | 不同点 | |
---|---|---|
layerY | 距离元素的左上角距离 | 受元素的定位的影响,会从本元素往上找到第一个定位的元素的左上角 |
offsetY | 距离元素左上角的距离 | 计算相对于本元素的左上角,不在乎定位问题,计算的是内交点。是IE浏览器的特有属性 |
实现拖拽功能
1. h5实现拖拽
---html---
<template>
<div>
<div
ref="drag"
@click.prevent="onNext"
@touchstart.prevent="down"
@touchmove.prevent="move"
@touchend.prevent="end"
:style="getPosition">
<img :src="imageUrl" class="icon" />
</div>
</div>
</template>
js
<script>
export default {
name: 'Drag',
props:{positionInit:Object}
data () {
return {
flags: false,
position: {
x: 0,
y: 0
},
positionStyles: {
top: 0,
left: 0,
right: 0,
bottom: 0
},
nx: '',
ny: '',
maxW: '',
maxH: '',
initLeftRight: 20
}
},
computed: {
getPosition () {
const objs = this.positionStyles
let str = ''
if (objs.top !== undefined && objs.top !== null) {
str = str + 'top:' + objs.top + 'px;'
}
if (objs.bottom !== undefined && objs.bottom !== null) {
str = str + 'bottom:' + objs.bottom + 'px;'
}
if (objs.left !== undefined && objs.left !== null) {
str = str + 'left:' + objs.left + 'px;'
}
if (objs.right !== undefined && objs.right !== null) {
str = str + 'right:' + objs.right + 'px;'
}
return str
}
},
wacth:{
//初始值positionInit 从父组件获得,获得后给positionStyles赋值,此处太easy省略
}
mounted () {
},
methods: {
onNext () { // 点击事件
this.flags = false
},
// 实现拖拽
down (event) {
if (!this.h5CmsIconData.draggable) return false
event.preventDefault()
const dragDiv = this.$refs.drag
let touch
if (event.touches) {
touch = event.touches[0]
} else {
touch = event
}
// 光标起始位置 touch.clientX/touch.clientY;
// 左偏移量 offsetLeft 上偏移量 offsetTop
this.position.x = touch.clientX - dragDiv.offsetLeft
this.position.y = touch.clientY - dragDiv.offsetTop
},
move (event) {
if (!this.h5CmsIconData.draggable) return false
event.preventDefault()
this.flags = true
const dragDiv = this.$refs.drag
if (this.flags) {
let touch
if (event.touches) {
touch = event.touches[0]
} else {
touch = event
}
// 添加限制:只允许在屏幕内拖动
// 页面宽高度减去悬浮框宽高
// offsetWidth和offsetHeight对象自身的的宽度/高度
this.maxW = document.documentElement.clientWidth - dragDiv.offsetWidth
// 页面高度
this.maxH = document.documentElement.clientHeight - dragDiv.offsetHeight
// 根据初始touch位置计算移动距离:元素移动位置=元素初始位置+(光标移动后的位置-光标点击时的初始位置)
this.nx = this.position.x + (touch.clientX - this.position.x)
this.ny = this.position.y + (touch.clientY - this.position.y)
if (this.nx <= 0) {
this.nx = this.initLeftRight
} else if (this.nx > this.maxW) {
this.nx = this.maxW
}
if (this.ny <= 0) {
this.ny = this.initLeftRight + this.commonHeaderHeight
} else if (this.ny >= this.maxH) {
this.ny = this.maxH
}
dragDiv.style.left = this.nx + 'px'
dragDiv.style.top = this.ny + 'px'
}
},
// 鼠标释放时候的函数
end (event) {
if (!this.h5CmsIconData.draggable) return false
this.commonHeaderHeight = document.getElementById('h5-common-header-box').clientHeight
const dragDiv = this.$refs.default_drag_comp
const divH = this.maxH - dragDiv.offsetHeight
const halfMaxWidth = (this.maxW - dragDiv.offsetWidth) / 2
// 吸边处理
if (!this.flags) { // 点击事件处理
this.onNext()
return false
}
// debugger
if (this.nx > halfMaxWidth) {
// 右吸边
dragDiv.style.left = this.maxW - (parseInt(dragDiv.clientWidth) / 2) + 'px' // 20 = initLeftRight
} else {
// 左吸边
dragDiv.style.left = this.initLeftRight + 'px'
}
// 上吸边 盒子高度顶部距离 < header
if (dragDiv.offsetTop < this.commonHeaderHeight) {
dragDiv.style.top = ((dragDiv.clientHeight / 2) + this.commonHeaderHeight) + 'px'
} else if (this.ny >= divH) { // 下吸边
dragDiv.style.top = this.maxH - ((parseInt(dragDiv.clientHeight) / 2) + this.initLeftRight) + 'px'
}
this.flags = false
}
}
}
</script>
Css 定位fixed布局,这个很简单,无需浪费文笔。
接下来重点讲一下pc拖拽的布局和遇到的坑
大概的坑有几个
1.drag事件,move结束的时候获取x,y值,最后都会为0,0. 所以每次拖拽就会回到初始位置,左上角。而且drag有兼容性问题(最后选择用了鼠标事件,详情看代码)
浏览器兼容情况如下图
2.点击事件和拖拽事件互斥。每次拖拽结束,都会触发点击事件。
3.切换页面以后,每次都会更新全局拖拽组件,导致每次切换以后悬浮挂件回到初始位置。(我司实现需要记住上一次位置,不管是跳转页面点击切换都不会回到初始值)
4.拖拽太快导致move事件失效,获取不了end的x,y值 导致结束位置不对.
接下来看代码
2.pc实现拖拽
<template>
<div>
<div
ref="drag"
draggable="true"
@mousedown.prevent="down"
:style="getPosition">
<img :src="imageUrl" class="icon" />
</div>
</div>
</template>
<script>
export default {
name: 'Drag',
props: {
positionInit: Object
},
data () {
return {
flags: false,//点击事件开关
position: {
x: 0,
y: 0
},
positionStyles: {
top: 0,
left: 0,
right: 0,
bottom: 0
},
nx: '',
ny: '',
maxW: '',
maxH: '',
initLeftRight: 20,
commonHeaderHeight: ''
}
},
computed: {
getPosition () {
const objs = this.positionStyles
let str = ''
if (objs.top !== undefined && objs.top !== null) {
str = str + 'top:' + objs.top + 'px;'
}
if (objs.bottom !== undefined && objs.bottom !== null) {
str = str + 'bottom:' + objs.bottom + 'px;'
}
if (objs.left !== undefined && objs.left !== null) {
str = str + 'left:' + objs.left + 'px;'
}
if (objs.right !== undefined && objs.right !== null) {
str = str + 'right:' + objs.right + 'px;'
}
return str
}
},
watch: {
//解决切换页面,导致挂件回到初始值 通过判断上一次的结束位置是否>0来记住位置
positionInit(val) {
const positionStyles = this.positionStyles
if ((positionStyles.left && positionStyles.left > 0) || (positionStyles.right && positionStyles.right > 0) || (positionStyles.top && positionStyles.top > 0) || (positionStyles.bottom && positionStyles.bottom > 0)) {
this.positionStyles = positionStyles
} else {
// 初始值赋值
this.positionStyles = positionInit
}
}
},
mounted () {
},
methods: {
goNext () { //点击事件 业务
this.flags = false
},
// 实现拖拽
down (event) {
const dragDiv = this.$refs.drag
let touch
if (event.touches) {
touch = event.touches[0]
} else {
touch = event
}
// 光标起始位置 touch.clientX/touch.clientY;
// 左偏移量 offsetLeft 上偏移量 offsetTop
this.position.x = touch.clientX - dragDiv.offsetLeft
this.position.y = touch.clientY - dragDiv.offsetTop
// 问题:拖拽太快导致move事件失效,获取不了end的x,y值 导致结束位置不对
// 方法:通过dom绑定鼠标move和end 事件
// 原理:vue 通过on 绑定只会绑定一次,如果太快容易失焦,通过dom绑定可解决
// 注意: dom绑定以后,需要清楚绑定,不然就会埋下bug
document.onmousemove = async (e) => {
await this.move(e)
}
document.onmouseup = (e) => {
this.end(e)
document.onmousemove = null
document.onmouseup = null
}
},
move (event) {
event.preventDefault()
const dragDiv = this.$refs.drag
this.flags = true
if (this.flags) {
let touch
if (event.touches) {
touch = event.touches[0]
} else {
touch = event
}
// dragDiv.offsetWidth和offsetHeight对象自身的的宽度/高度
this.maxW = document.documentElement.clientWidth - dragDiv.offsetWidth
this.maxH = document.documentElement.clientHeight - dragDiv.offsetHeight
this.positionStyles.left = touch.clientX
this.positionStyles.top = touch.clientY
}
},
end (event) {
if (!this.flags) { // 点击事件处理
this.goNext()
return
}
let touch
if (event.touches) {
touch = event.touches[0]
} else {
touch = event
}
this.checkBian(touch.clientX, touch.clientY)
this.flags = false
},
checkBian (x, y) {
let positionStyles = this.positionStyles
this.commonHeaderHeight = document.getElementById('pc-common-header-box').clientHeight
const dragDiv = this.$refs.drag
dragDiv.removeEventListener('mousemove', this.move, true)
dragDiv.removeEventListener('mouseup', this.move, true)
const divH = this.maxH - dragDiv.offsetHeight
const halfMaxWidth = this.maxW / 2
if (x > halfMaxWidth) {
// 右吸边
positionStyles = {
...positionStyles,
left: null,
right: this.initLeftRight
}
} else {
// 左吸边
positionStyles = {
...positionStyles,
left: this.initLeftRight,
right: null
}
}
// 上吸边 盒子高度顶部距离 < header
if (y < this.commonHeaderHeight) {
positionStyles = {
...positionStyles,
top: this.initLeftRight + this.commonHeaderHeight,
bottom: null
}
} else if (y >= divH) { // 下吸边
positionStyles = {
...positionStyles,
top: null,
bottom: this.initLeftRight
}
} else { // 随拖动值clientY 移动
positionStyles = {
...positionStyles,
top: y,
bottom: null
}
}
this.$set(this, 'positionStyles', positionStyles)
}
}
}
</script>
css fixed定位省略
结语
到这里我们就已经把拖拽效果用Vue实现了,我们用了两种不同的方式实现了拖拽,但实际上换汤不换药,我们需要弄清楚pageY、screenY、clientY、layerY、offsetY等区别。当然我们同时也学习了Vue的一些方法,例如自定义指令等。
欢迎各位大佬指正,有更好的实现方式可多留言交流