前端收集Vue

Vue 实现可拖拽弹窗

2019-05-27  本文已影响113人  LingSun

一、实现原理

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、组件使用到的相关属性
5、组件自定义指令

https://cn.vuejs.org/v2/guide/custom-directive.html

6、组件使用到的相关事件
el.onmousedown = function(e){
    console.log('鼠标已按下:', e)
}
el.onmousemove = function(e){
    console.log('鼠标移动中:', e)
}
el.onmouseup = function(e){
    console.log('鼠标已松开:', e)
}
// 注:el 表示当前触发的元素
7、实现思路
el.onmousedown = ((event) => {
  let mouseX = event.clientX - vnode.offsetLeft
  let mouseY = event.clientY - vnode.offsetTop
})
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.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%"
})
// 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>
上一篇下一篇

猜你喜欢

热点阅读