一、实现原理
1、获取鼠标在div中的位置
2、设置 div 的 left 和 top 使其跟随鼠标位置移动,达到拖拽的效果
二、实现步骤
1、UI
<template>
<div v-if="visible" class="my_dialog">
<!-- 遮罩层 -->
<div class="my_dialog_mask"></div>
<div class="my_dialog_box" id="my_dialog_box" v-drag>
<!-- 标题 -->
<div class="my_dialog_title">
{{title}}
<span class="my_dialog_close" @click="cancel">X</span>
</div>
<!-- 内容 -->
<div class="my_dialog_content">
<slot></slot>
</div>
<!-- 底部按钮 -->
<div class="my_dialog_bottom">
<button class="btn cancelBtn" v-if="showCancelButton" @click="cancel">{{canceltext}}</button>
<button class="btn confirmBtn" @click="confirm">{{confirmtext}}</button>
</div>
</div>
</div>
</template>
2、组件定义props
visible:控制弹窗显示,false 不显示,true 显示
title:弹窗标题,默认 提示
confirmtext:确认按钮文案,默认 确定
canceltext: 取消按钮文案,默认 取消
showCancelButton:是否显示取消按钮,false 否,true 是,默认 true
3、组件事件回调 $emit
methods: {
cancel: function () {
// .sync 实现弹窗显示 or 隐藏
this.$emit("update:visible", false)
this.$emit("cancel")
},
confirm: function () {
this.$emit("confirm")
},
},
4、组件使用到的相关属性
- event.clientX / event.clientY:鼠标触发时指针相对于浏览器可视区域的水平 / 垂直坐标
- vnode.offsetLeft / vnode.offsetTop属性:当前元素距离某个定位父辈元素左边 / 顶部的距离
- vnode.offsetWidth / vnode.offsetHeight:当前元素的宽 / 高度,包括边框和填充
- window.innerHeight / window.innerHeightWidth:获取当前页面可视区的宽高(包括滚动条)
5、组件自定义指令
https://cn.vuejs.org/v2/guide/custom-directive.html
6、组件使用到的相关事件
- onmousedown 鼠标按下事件
- onmousemove 鼠标移动事件
- onmouseup 鼠标松开事件
el.onmousedown = function(e){
console.log('鼠标已按下:', e)
}
el.onmousemove = function(e){
console.log('鼠标移动中:', e)
}
el.onmouseup = function(e){
console.log('鼠标已松开:', e)
}
// 注:el 表示当前触发的元素
7、实现思路
- 给弹窗绑定onmousedown事件,获取鼠标在弹窗中按下的位置(以弹窗左上角为原点)
el.onmousedown = ((event) => {
let mouseX = event.clientX - vnode.offsetLeft
let mouseY = event.clientY - vnode.offsetTop
})
- document绑定onmousemove事件,获取当前的鼠标位置,当前鼠标位置 - 鼠标在弹窗中的相对位置,通过style设置弹窗的当前位置
document.onmousemove = ((event) => {
let left, top
// 获取新的鼠标位置(event.clientX, event.clientY)
// 弹窗应该在的位置(left, top)
// (mouseX, mouseY) 鼠标按下时的坐标
left = event.clientX - mouseX
top = event.clientY - mouseY
// 赋值移动
vnode.style.left = left + 'px'
vnode.style.top = top + 'px'
})
- 鼠标松开解绑document的鼠标事件onmousedown,onmousemove
document.onmouseup = (() => {
document.onmousemove = document.onmouseup = null
})
- 控制弹窗只能在浏览器可视区内被拖拽,需要设置水平和垂直方向的最大最小移动位置,在“赋值移动”前添加下面代码
// 获取弹窗在页面中距X轴的最小、最大 位置
let minX = -vnode.offsetWidth / 2 + 100
let maxX = window.innerWidth + vnode.offsetWidth / 2 - 100
if (left <= minX) {
left = minX
} else if (left >= maxX) {
left = maxX
}
// 获取弹窗在页面中距Y轴的最小、最大 位置
let minY = vnode.offsetHeight / 2
let maxY = window.innerHeight + vnode.offsetHeight / 2 - 100
if (top <= minY) {
top = minY
} else if (top >= maxY) {
top = maxY
}
- 浏览器可视区大小变化时重置弹窗的位置到初始化状态
window.onresize = (() => {
vnode.style.left = "50%"
vnode.style.top = "50%"
})
- 控制鼠标按下弹窗指定区域才能拖拽弹窗,在 onmousedown 事件中添加下面代码
// my_dialog_title 指定区域对应元素的类名
if (event.target.className !== "my_dialog_title") {
return
}
8、完整拖拽指令
directives: {
drag: {
inserted: function (el, binding, vnode) {
vnode = vnode.elm
el.onmousedown = ((event) => {
if (event.target.className !== "my_dialog_title") {
return
}
// (clientX, clientY)点击位置距离当前可视区域的坐标(x,y)
// offsetLeft, offsetTop 距离上层或父级的左边距和上边距
// 获取鼠标在弹窗中的位置
let mouseX = event.clientX - vnode.offsetLeft
let mouseY = event.clientY - vnode.offsetTop
// 绑定移动和停止函数
document.onmousemove = ((event) => {
let left, top
// 获取新的鼠标位置(event.clientX, event.clientY)
// 弹窗应该在的位置(left, top)
left = event.clientX - mouseX
top = event.clientY - mouseY
// offsetWidth、offsetHeight 当前元素的宽度
// innerWidth、innerHeight 浏览器可视区的宽度和高度
// 获取弹窗在页面中距X轴的最小、最大 位置
let minX = -vnode.offsetWidth / 2 + 100
let maxX = window.innerWidth + vnode.offsetWidth / 2 - 100
if (left <= minX) {
left = minX
} else if (left >= maxX) {
left = maxX
}
// 获取弹窗在页面中距Y轴的最小、最大 位置
let minY = vnode.offsetHeight / 2
let maxY =window.innerHeight + vnode.offsetHeight / 2 - 100
if (top <= minY) {
top = minY
} else if (top >= maxY) {
top = maxY
}
// 赋值移动
vnode.style.left = left + 'px'
vnode.style.top = top + 'px'
})
document.onmouseup = (() => {
document.onmousemove = document.onmouseup = null
})
})
window.onresize = (() => {
vnode.style.left = "50%"
vnode.style.top = "50%"
})
}
}
}
9、引用
使用 import 引入
eg: import myDialog from '@/components/myDialog'
<template>
<div class="my_page">
<button @click="isShow = true">打开</button>
<my-dialog class="dialog" :visible.sync="isShow" :title="title" :canceltext="canceltext" :confirmtext="confirmtext" @confirm="onConfirm" @cancel="onCancel">
这是个弹窗
</my-dialog>
</div>
</template>
<script>
// 引入弹窗
import myDialog from '@/components/myDialog'
export default {
data() {
return {
isShow: false,
title: '我的弹窗',
canceltext: '关闭',
confirmtext: '提交'
}
},
components: {
myDialog
},
methods: {
onConfirm() { },
onCancel() { }
}
}
</script>
<style>
.my_page {
text-align: center;
}
</style>
10、CSS
<style>
.my_dialog {
position: fixed;
z-index: 99;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
.my_dialog_mask {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
background-color: #000;
opacity: 0.5;
}
.my_dialog_box {
position: absolute;
width: 550px;
background: #fff;
top: 50%;
left: 50%;
max-width: 100%;
border-radius: 3px;
overflow: hidden;
transform: translate(-50%, -50%);
}
.my_dialog_content {
min-height: 100px;
overflow-x: hidden;
overflow-y: auto;
position: relative;
padding: 20px;
text-align: left;
box-sizing: border-box;
}
.my_dialog_title {
cursor: all-scroll;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
top: 0;
left: 0;
height: 40px;
line-height: 40px;
border-bottom: 1px solid #e7e8eb;
color: #000;
font-size: 18px;
font-family: \5fae\8f6f\96c5\9ed1;
padding: 0 31px 0 18px;
text-align: left;
user-select: none;
}
.my_dialog_close {
cursor: pointer;
position: absolute;
top: 50%;
margin-top: -8px;
right: 20px;
width: 16px;
height: 16px;
line-height: 16px;
color: #ccc;
}
.my_dialog_close:hover {
color: #409eff;
}
.my_dialog_bottom {
margin: 0;
padding: 16px 0;
text-align: center;
border-top: 1px solid transparent;
}
.btn {
min-width: 60px;
text-align: center;
vertical-align: middle;
font-size: 14px;
padding: 5px 15px;
border-radius: 3px;
text-decoration: none;
border-radius: 3px;
cursor: pointer;
}
.my_dialog_bottom .cancelBtn:focus,
.my_dialog_bottom .cancelBtn:hover {
color: #409eff;
background: #ecf5ff;
border: 1px solid #b3d8ff;
}
.my_dialog_bottom .confirmBtn:focus,
.my_dialog_bottom .confirmBtn:hover {
background: #66b1ff;
border: 1px solid #66b1ff;
color: #fff;
}
.my_dialog_bottom .confirm_btn .marginLeft {
margin-left: 10px;
}
.cancelBtn {
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
}
.confirmBtn {
border: 1px solid #409eff;
background-color: #409eff;
color: #fff;
}
button + button {
margin-left: 15px;
}
</style>
网友评论