#先看效果
已经填平的坑:
1.元素被拖出屏幕外问题:计算元素距离边缘的距离,判断是否已经贴近边缘,防止拖出屏幕;
2.拖拽结束(鼠标抬起)时会触发点击事件问题:使用定时器记录鼠标按下到抬起的时间,如果大于100ms,则视为拖拽,不触发点击事件。
使用el-popover组件可以自适应弹框的弹出位置,防止靠近边缘时打开的弹框位置超出屏幕
.popperClass类名一定要写在全局样式中,不能有样式隔离属性,否则不会生效。
在app.vue中(如果要在非全局实现拖拽,将关键源码移动并修改定位属性即可)
vue2/vue3 源码已测试均能实现一致效果
vue2+element ui源码(vue3源码往下):
<template>
<div id="app">
<el-popover
width="400"
:popper-class="'popperClass'"
trigger="manual"
v-model="ispopover">
<!-- 弹出框内容 -->
<div class="myList888" style="width: 200px;height: 200px;background-color: #ccc;"></div>
<!-- 弹出框触发元素 -->
<div
slot="reference"
class="dragBox"
:style="{
left: dragBoxsite.left + 'px',
top: dragBoxsite.top + 'px'
}"
@mousedown="startmove"
@click="ispopoverChange"
>
</div>
</el-popover>
<div class="Mask" v-if="maskFlag || ispopover" @click="ispopover = false"></div>
<!-- <div class="Mask2"></div> -->
<router-view />
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
ismove: false, // 是否正在拖拽
dragBoxsite: { left: 0, top: 0 }, // 拖拽前的位置
thisW:1920,
thisH:1080,
ispopover:false,
maskFlag:false,
timer:null,
timerNum:0, // 拖拽的累计时间
thisdragBoxsite: { left: 0, top: 0 }, // 拖拽中的位置
};
},
mounted() {
// 获取屏幕的宽高
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
// 此处获取屏幕的宽高 如果你只想在单一容器元素内拖拽,可以修改此处的代码宽高为你容器元素的宽高
this.thisW = screenWidth;
this.thisH = screenHeight;
},
methods: {
ispopoverChange(){
if(this.timerNum > 0){
// 拖拽不触发点击事件
this.timerNum = 0;
return
}
this.ispopover = !this.ispopover
this.timerNum = 0;
},
// 开始拖拽
startmove(e) {
this.maskFlag = true; //打开遮罩防止拖拽时选中页面的内容
if(!this.timer){
this.timer = setInterval(() => {
this.timerNum ++
if(this.timerNum > 0){
// 弹出框为打开状态 拖拽时应关闭弹框
this.ispopover = false;
}
}, 100);
document.addEventListener('mousemove', this.handlemove);
document.addEventListener('mouseup', this.stopmove);
}
this.ismove = true;
this.thisdragBoxsite.left=e.clientX - this.dragBoxsite.left
this.thisdragBoxsite.top=e.clientY - this.dragBoxsite.top
},
// 拖拽中
handlemove(e) {
if (!this.ismove) return;
let w = 50; // 被拖拽元素的宽
let h = 50; // 被拖拽元素的高
this.dragBoxsite.left = (e.clientX - this.thisdragBoxsite.left) + w >= this.thisW ? this.thisW - w :(e.clientX - this.thisdragBoxsite.left) <= 0 ? 0 : (e.clientX - this.thisdragBoxsite.left)
this.dragBoxsite.top = (e.clientY - this.thisdragBoxsite.top) + h >= this.thisH ? this.thisH - h :(e.clientY - this.thisdragBoxsite.top) <= 0 ? 0 : (e.clientY - this.thisdragBoxsite.top)
},
// 拖拽结束
stopmove() {
document.removeEventListener('mousemove', this.handlemove);
document.removeEventListener('mouseup', this.stopmove);
this.maskFlag = false; //关闭遮罩层
clearInterval(this.timer); //清除定时器
this.timer = null;
this.ismove = false;
},
},
};
</script>
<style scoped>
.dragBox {
width: 50px;
height: 50px;
position: fixed;
cursor: move;
background-color: red;
z-index: 9999999;
}
.Mask{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 9999997;
}
.Mask2{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: #ccc;
z-index: 9999996;
}
</style>
<style>
.popperClass{
z-index: 9999998 !important;
}
</style>
vue3+Element Plus 源码:
<template>
<div id="app">
<el-popover
width="400"
:popper-class="'popperClass'"
trigger="manual"
v-model:visible="ispopover">
<!-- 弹出框内容 -->
<div class="myList888" style="width: 200px;height: 200px;background-color: #ccc;"></div>
<!-- 弹出框触发元素 -->
<template #reference>
<div
class="dragBox"
:style="{
left: dragBoxsite.left + 'px',
top: dragBoxsite.top + 'px'
}"
@mousedown="startmove"
@click="ispopoverChange"
></div>
</template>
</el-popover>
<div class="Mask" v-if="maskFlag || ispopover" @click="ispopover = false"></div>
<!-- <div class="Mask2"></div> -->
<router-view />
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue';
export default {
name: 'App',
setup() {
const ismove = ref(false);
const ispopover = ref(false);
const maskFlag = ref(false);
const dragBoxsite = reactive({ left: 0, top: 0 });
const thisW = ref(1920);
const thisH = ref(1080);
const timer = ref(null);
const timerNum = ref(0);
const thisdragBoxsite = reactive({ left: 0, top: 0 });
// 获取屏幕的宽高
onMounted(() => {
thisW.value = window.innerWidth;
thisH.value = window.innerHeight;
});
const ispopoverChange = () => {
if (timerNum.value > 0) {
// 拖拽不触发点击事件
timerNum.value = 0;
return;
}
ispopover.value = !ispopover.value;
timerNum.value = 0;
};
// 开始拖拽
const startmove = (e) => {
maskFlag.value = true; // 打开遮罩防止拖拽时选中页面的内容
if (!timer.value) {
timer.value = setInterval(() => {
timerNum.value++;
if (timerNum.value > 0) {
// 弹出框为打开状态 拖拽时应关闭弹框
ispopover.value = false;
}
}, 100);
document.addEventListener('mousemove', handlemove);
document.addEventListener('mouseup', stopmove);
}
ismove.value = true;
thisdragBoxsite.left = e.clientX - dragBoxsite.left;
thisdragBoxsite.top = e.clientY - dragBoxsite.top;
};
// 拖拽中
const handlemove = (e) => {
if (!ismove.value) return;
let w = 50; // 被拖拽元素的宽
let h = 50; // 被拖拽元素的高
dragBoxsite.left = Math.min(Math.max(e.clientX - thisdragBoxsite.left, 0), thisW.value - w);
dragBoxsite.top = Math.min(Math.max(e.clientY - thisdragBoxsite.top, 0), thisH.value - h);
};
// 拖拽结束
const stopmove = () => {
document.removeEventListener('mousemove', handlemove);
document.removeEventListener('mouseup', stopmove);
maskFlag.value = false; // 关闭遮罩层
clearInterval(timer.value); // 清除定时器
timer.value = null;
ismove.value = false;
};
return {
ispopover,
maskFlag,
dragBoxsite,
thisW,
thisH,
timer,
timerNum,
thisdragBoxsite,
ispopoverChange,
startmove,
handlemove,
stopmove,
};
},
};
</script>
<style scoped>
.dragBox {
width: 50px;
height: 50px;
position: fixed;
cursor: move;
background-color: red;
z-index: 9999999;
}
.Mask {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 9999997;
}
.Mask2 {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: #ccc;
z-index: 9999996;
}
</style>
<style>
.popperClass {
z-index: 9999998 !important;
}
</style>
这是一段废话,不用看