可拖拽的弹窗
在刚刚重构完的项目中使用到了
element ui
框架,踩了不少坑也学到了不少的东西。其中比较麻烦的是它的dialog
弹窗组件是无法移动拖拽的,然而客户又强烈的要求一定要有这个功能,所以就自己写了个可拖拽的弹窗组件。虽然拖拽起来不是很流畅,但是也算是满足要求了。
实现原理
主要的实现原理还是获取鼠标在div
中的位置,获取位置后设置div
的left
和top
来达到div
跟随鼠标移动的效果。因为写的是vue
,所以利用了vue
的自定义指令来操作dom
。
实现步骤
设计盒子ui
老实说,我经常被吐槽没有审美,设计的样式总是被喷。好在这次是
dialog
弹窗,网上有大把的参考样式。我大体参考了layer
的弹窗做出了一个山寨弹窗。html代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30<template>
<div class="m_showBox" :class="skin">
<div class="showBox_mask"></div>
<div class="loading_wrap" v-if="buttonstatus === 1"></div>
<div class="pop_box" id="pox-box" v-drag>
<p class="pop_box_title">
{{title || "提示"}}
<span class="pop_box_close" @click="cancel"></span>
</p>
<div class="pop_box_content">
<slot></slot>
</div>
<div class="pop_box_bottom">
<a href="javascript:;"
class="cancel_btn"
@click="cancel">{{canceltext || "取消"}}</a>
<a href="javascript:;"
class="confirm_btn"
v-if="type === 'confirm'"
:class="{widths: buttonstatus === 1}"
@click="confirm">
<svg viewBox="25 25 50 50" class="u-circular" v-if="buttonstatus === 1">
<circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
</svg>
<span :class="{'marginLeft': buttonstatus === 1}">{{confirmtext || '确定'}}</span>
</a>
</div>
</div>
</div>
</template>css
代码太长放到github
上了[vueDrag.vue][1]效果图:
设计要点:
- 背景遮罩
我这里选择了使用了3个遮罩板,第一块是覆盖全屏幕的白色遮罩m_showBox
使用fixed定位,让弹窗的所有内容与浏览器之间不会出现留白。第2块就是上图看到的灰色背景showBox_mask
,用来突显弹窗。最后一块是点击确定的遮罩窗loading_wrap
,来防止提交ajax
时,用户点击按钮或修改弹窗数据。 - 弹窗构成
这里的弹窗就包括标题,内容和底部部分。内容部分通过插槽插入内容,底部按钮通过svg
来实现提交加载的loading
效果。
- 背景遮罩
定义组件props
通过传入的props
值来设置弹窗的样式和文案。
自定义事件实现按钮回调
confirm
和cancel
自定义事件,定义自定义按钮事件,使用$emit
触发。1
2
3
4
5
6
7
8
9
10
11methods: {
cancel: function () {
this.$emit("cancel");
},
confirm: function () {
if (this.buttonstatus === 1) {
return;
}
this.$emit("confirm");
},
},
自定义指令drag
实现拖拽效果
vue
的directives
。
通过vue
自定义指令获取绑定的元素,在对DOM
进行操作。关于更多vue
自定义指令用法,移步自定义指令
相关属性(事件对象event
,dom
元素,window
对象)。
event.clientX
:clientX
事件属性返回当事件被触发时鼠标指针向对于浏览器可视区域的水平坐标。event.clientY
:clientY
事件属性返回当事件被触发时鼠标指针向对于浏览器页面可视区域的垂直坐标。offsetLeft/offsetLeftTop
属性:可以返回当前元素距离某个定位父辈元素左边与顶部的距离(虽然我的父级遮罩层有了定位,但是它的宽高都是与body
保持一致的)。offsetWidth/offsetHeight
: 返回任何一个元素宽/高度,包括边框和填充window.innerHeight/Width
: 获取当前页面可视区的宽高(包括滚动条)。
相关事件
实现代码
1 | directives: { |
代码解析
- 给弹窗绑定
onmousedown
事件,获取到鼠标在弹窗中的位置(以弹窗左上角为原点)。 document
绑定onmousemove
事件,获取当前的鼠标位置,当前鼠标位置减去鼠标在弹窗的相当位置即可得到此时弹窗应该处于的位置。然后在通过style
设置弹窗的位置。- 鼠标松开解绑
document
的鼠标事件。
注意点:
- 弹窗要一直在页面可视区移动,最大的移动距离就是可视区的宽高减去盒子本身的宽高(还要考虑到浏览器的滚动条的宽高,我的浏览器滚动条是自己设置的,高度为0,宽度为10)。
window.innerHeight - vnode.offsetHeight / 2
;(window.innerWidth - vnode.offsetWidth / 2) - 10
; - 只有弹窗标题才能拖拽,所以判断非标题部分之间
return
。 - 浏览器窗口大小改变会影响弹窗的位置,监听改变浏览器窗口改变把弹窗居中。
使用
单独引用
- 下载
drag.vue
。vueDrag.vue。 控制弹窗的显示隐藏通过
v-if
绑定data
里的数据即可。1
2
3
4
5
6
7
8
9
10
11
12<transition name="el-fade-in">
<v-drag v-if="isShow" :tilte="title" :type="type" @confirm="confirmSubmit" @cancel="cancel" :buttonstatus="buttonstatus">
<el-form label-width="100px">
<el-form-item label="用户名称:">
<el-input placeholder="请输入用户名" v-model="username"></el-input>
</el-form-item>
<el-form-item label="密码:">
<el-input placeholder="请输入密码" v-model="password"></el-input>
</el-form-item>
</el-form>
</v-drag>
</transition>相关的属性和emit方法需要自己定义
v-cli全局引入
src
目录下新建components
目录,下载vueDrag.vue
到此目录下。components
目录下新建index.js
1
2
3
4
5import VueDrag form ./vueDrag.vue
export default function install(Vue) {
Vue.component("app-drag", VueDrag);
}main.js
中加入代码1
2import appComponents from "./components/index.js";
Vue.use(appComponents);页面中使用
<app-drag></app-drag>
结语
关于这个组件我觉得还有很多优化的地方,望各位大佬给出意见。