组件文件
某天突然有这么个需求,看到了个例子,拿来优化了下,可以完美在容器内部自由拖动并自动吸附, 部分参数和移动端拖动方法还需优化,PC端可用,记录一下
<template>
<div
ref="floatDrag"
class="float-position"
:style="{ left: left + 'px', top: top + 'px', zIndex: zIndex }"
@touchmove.prevent
@mousemove.prevent
@mousedown="mouseDown"
@mouseup="mouseUp"
>
<slot v-bind:canClick="canClick"></slot>
</div>
</template>
<script>
export default {
name: "DragBall",
props: {
distanceRight: {
type: Number,
default: 0,
},
distanceBottom: {
type: Number,
default: 100,
},
isScrollHidden: {
type: Boolean,
default: false,
},
isCanDraggable: {
type: Boolean,
default: true,
},
zIndex: {
type: Number,
default: 999,
},
mTop: {
type: Number,
default: 50,
},
positon: {
type: String,
default: "right",
},
},
//data 域
data() {
return {
containerWidth: null,
containerHeight: null,
left: 0,
top: 0,
timer: null,
currentTop: 0,
mousedownX: 0,
mousedownY: 0,
offsetX: 0,
offsetY: 0,
canClick: true,
};
},
created() {},
mounted() {
this.isCanDraggable &&
this.$nextTick(() => {
this.floatDrag = this.$refs.floatDrag;
// // 获取元素位置属性
this.floatDragDom = this.floatDrag.getBoundingClientRect();
this.containerWidth = this.$refs.floatDrag.parentElement.clientWidth;
this.containerHeight = this.$refs.floatDrag.parentElement.clientHeight;
// 计算父元素左上角相对于窗口的偏移量
this.offsetX =
this.$refs.floatDrag.parentElement.getBoundingClientRect().left;
this.offsetY =
this.$refs.floatDrag.parentElement.getBoundingClientRect().top;
this.top = this.mTop;
this.left = this.containerWidth - this.floatDragDom.width;
this.initDraggable();
});
this.top =
this.isScrollHidden &&
window.addEventListener("scroll", this.handleScroll);
window.addEventListener("resize", this.handleResize);
},
methods: {
/**
* 设置滚动监听
* 设置滚动时隐藏悬浮按钮,停止时显示
*/
handleScroll() {
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.handleScrollEnd();
}, 200);
this.currentTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (this.left > this.containerWidth / 2) {
// 判断元素位置再左侧还是右侧
this.left = this.containerWidth + this.floatDragDom.width;
} else {
this.left = -this.floatDragDom.width;
}
},
/**
* 滚动结束
*/
handleScrollEnd() {
let scrollTop =
document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop === this.currentTop) {
console.log(this.left);
if (this.left > this.containerWidth / 2) {
// 判断元素位置再左侧还是右侧
this.left = this.containerWidth - this.floatDragDom.width;
} else {
this.left = 0;
}
clearTimeout(this.timer);
}
},
/**
* 窗口resize监听
*/
handleResize() {
this.containerWidth = document.documentElement.containerWidth;
this.containerHeight = document.documentElement.containerHeight;
this.checkDraggablePosition();
},
/**
* 初始化draggable
*/
initDraggable() {
this.floatDrag.addEventListener("touchstart", this.toucheStart);
this.floatDrag.addEventListener("touchmove", (e) => this.touchMove(e));
this.floatDrag.addEventListener("touchend", this.touchEnd);
},
mouseDown(e) {
const event = e || window.event;
// 不是左键
if (event.button !== 0) {
return;
}
this.disabled = false;
this.mousedownX = event.screenX;
this.mousedownY = event.screenY;
const that = this;
let floatDragWidth = this.floatDragDom.width / 2;
let floatDragHeight = this.floatDragDom.height / 2;
if (event.preventDefault) {
event.preventDefault();
}
this.canClick = false;
this.floatDrag.style.transition = "none";
console.log("containerHeight :>> ", this.containerHeight);
document.onmousemove = function (e) {
var event = e || window.event;
that.left = event.clientX - floatDragWidth - that.offsetX;
that.top = event.clientY - floatDragHeight - that.offsetY;
if (that.left < 0) that.left = 0;
if (that.top < 0) that.top = 0;
if (that.left >= that.containerWidth - floatDragWidth * 2) {
that.left = that.containerWidth - floatDragWidth * 2;
}
if (that.top >= that.containerHeight - floatDragHeight * 2) {
console.log("that.top :>> ", that.top);
that.top = that.containerHeight - floatDragHeight * 2;
}
};
},
mouseUp(e) {
const event = e || window.event;
//判断只是单纯的点击,没有拖拽
if (
this.mousedownY == event.screenY &&
this.mousedownX == event.screenX
) {
this.canClick = true;
this.$emit("handlepaly");
}
document.onmousemove = null;
this.checkDraggablePosition();
this.floatDrag.style.transition = "all 0.3s";
setTimeout(() => {
this.canClick = true;
}, 300);
},
toucheStart() {
this.canClick = false;
this.floatDrag.style.transition = "none";
},
touchMove(e) {
this.canClick = true;
if (e.targetTouches.length === 1) {
// 单指拖动
let touch = event.targetTouches[0];
this.left = touch.clientX - this.floatDragDom.width / 2;
this.top = touch.clientY - this.floatDragDom.height / 2;
}
},
touchEnd() {
if (!this.canClick) return; // 解决点击事件和touch事件冲突的问题
this.floatDrag.style.transition = "all 0.3s";
this.checkDraggablePosition();
},
/**
* 判断元素显示位置
* 在窗口改变和move end时调用
*/
checkDraggablePosition() {
// 默认靠右
// if (this.left + this.floatDragDom.width / 2 >= this.containerWidth / 2) {
// // 判断位置是往左往右滑动
this.left = this.containerWidth - this.floatDragDom.width;
// } else {
// this.left = 0;
// }
if (this.top < 0) {
// 判断是否超出屏幕上沿
this.top = 0;
}
if (this.top + this.floatDragDom.height >= this.containerHeight) {
// 判断是否超出屏幕下沿
this.top = this.containerHeight - this.floatDragDom.height;
}
},
},
beforeDestroy() {
window.removeEventListener("scroll", this.handleScroll);
window.removeEventListener("resize", this.handleResize);
},
};
</script>
<style>
/* html,
body {
overflow: hidden;
} */
</style>
<style scoped lang="scss">
.float-position {
position: absolute;
z-index: 10003;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
</style>
使用示例
容器container需要设置相对定位
<div class="container">
<dragFreely style="cursor: pointer">
<template v-slot:default="slotProps">
<selectedListTag
v-if="showSelectTag"
:num="selectedNum"
:canClick="slotProps.canClick"
/>
</template>
</dragFreely>
</div>
子组件需要在porps接受canClick并在点击事件响应前进行判断
function(){
if(!this.canclick) return;
// 你的业务代码
}