美文网首页前端收集Vue
Vue 实现可拖拽弹窗

Vue 实现可拖拽弹窗

作者: LingSun | 来源:发表于2019-05-27 17:35 被阅读113次

    一、实现原理

    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>
    

    相关文章

      网友评论

        本文标题:Vue 实现可拖拽弹窗

        本文链接:https://www.haomeiwen.com/subject/nsdhtctx.html