前端杂货铺关于VUE项目实际运用知识点

vue编写可推拽的弹窗

2020-07-13  本文已影响0人  前端小黑

1. 前言

早期在深圳工作的时候编写过一个可推拽的vue弹窗组件,实现的代码和写法有些简陋和不规范。最近回武汉工作刚好也有了这个需求,所以重新修改了下代码。

2. 实现原理

主要的实现原理还是获取鼠标在div中的位置,获取位置后设置divlefttop来达到div跟随鼠标移动的效果。因为写的是vue,所以利用了vue的自定义指令来操作dom

3. 搭建主体ui

3-1. 代码结构

<template>
  <transition name="el-fade-in-linear">
    <div class="dialog_box" v-if="dialogVisible">
      <div class="dialog_mask"></div>
      <div
        class="loading_wrap"
        v-if="confirmLoading"
      ></div>
      <div
        :style="{ width: dialogWidth }"
        class="normal_dialog"
        v-drag
      >
        <div class="dialog_header">
          <div class="header_title fl">{{ title }}</div>
          <div class="header_button_box fr">
            <i
              class="el-icon-close"
              @click="closeDialog"
            />
          </div>
        </div>
        <div
          :style="{ height: bodyHeight }"
          class="dialog_body"
        >
          <!-- 弹窗中心区域 -->
          <slot/>
        </div>
        <div
          class="dialog_footer"
          :style="{ 'textAlign': footerAlign }"
        >
          <el-button
            plain
            class="edit-dialog-btn"
            size="small"
            type="info"
            @click="cancel"
          >{{ cancelText }}
          </el-button>
          <el-button
            v-if="dialogType === 'confirm'"
            class="edit-dialog-btn"
            size="small"
            type="primary"
            :loading="confirmLoading"
            @click="confirm"
          >{{ confirmText }}
          </el-button>
        </div>
      </div>
    </div>
  </transition>
</template>
.dialog_box {
  position: fixed;
  z-index: 99;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;

  .dialog_mask {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    background-color: #000;
    opacity: 0.5;
  }

  .loading_wrap {
    position: absolute;
    z-index: 999;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    background: transparent;
  }

  .normal_dialog {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #FFFFFF;
    box-shadow: 0px 0px 0px 0px;
    border-radius: 10px;
    border: 1px solid #DDDDDD;
    z-index: 1000;
    box-sizing: border-box;

    .dialog_header {
      overflow: hidden;
      height: 50px;
      line-height: 55px;
      padding: 0 15px;
      font-size: 15px;
      color: #000000;
      border-bottom: 1px solid #EEEEEE;
      cursor: move;

      .header_button_box {
        cursor: pointer;

        i {
          display: inline-block;
          font-size: 18px;
          color: #666666;
          margin-left: 10px;
        }
      }
    }

    .dialog_body {
      color: #606266;
      font-size: 14px;
      overflow: auto;
    }

    .dialog_footer {
      width: 100%;
      border-top: 1px solid #EEEEEE;
      padding: 10px;
      box-sizing: border-box;

      .edit-dialog-btn {
        width: 70px;
      }
    }
  }
}

3-2. 设计要点

4. 定义组件props

props 类型 描述
dialogShow Boolean 设置弹窗的显示隐藏
dialogWidth String 设置弹窗的整体宽度
title String 弹窗的标题
dialogType String 弹窗的类型 (confirm, info)
footerAlign String 底部按钮的对齐方式 (left, center, right)
confirmText String 确定按钮的文案
cancelText String 取消按钮的文案
confirmLoading Boolean 确定按钮的加载遮罩

5. 自定义事件实现按钮回调

点击头部的x按钮关闭弹窗和点击确认,取消按钮的$emit回调事件

methods: {
    // emit按钮的点击事件给父组件
    cancel() {
      this.$emit("cancel");
    },
    confirm() {
      this.$emit("confirm");
    },
    // 关闭弹窗
    closeDialog() {
      this.dialogVisible = false;
    }
},

6. 自定义指令drag实现拖拽效果

6-1. vuedirectives

通过vue自定义指令获取绑定的元素,在对DOM进行操作。关于更多vue自定义指令用法,移步自定义指令

6-2. 相关属性(事件对象eventdom元素,window对象)

  1. event.clientXclientX事件属性返回当事件被触发时鼠标指针向对于浏览器可视区域的水平坐标。
  2. event.clientYclientY事件属性返回当事件被触发时鼠标指针向对于浏览器页面可视区域的垂直坐标。
  3. offsetLeft/offsetLeftTop属性:可以返回当前元素距离某个定位父辈元素左边与顶部的距离(虽然我的父级遮罩层有了定位,但是它的宽高都是与body保持一致的)。
  4. offsetWidth/offsetHeight: 返回任何一个元素宽/高度,包括边框和填充
  5. window.innerHeight/Width: 获取当前页面可视区的宽高(包括滚动条)。

6-3. 相关事件

props 描述
onmusedown 按下鼠标时触发
onmusemove 按下鼠标过程中移动鼠标触发
onmuseup 松开鼠标时触发
onresize 页面可视化区域变化触发

6-4. 自定义指令代码

directives: {
    drag: {
      inserted(el, binding, vnode) {
        vnode = vnode.elm;
        el.onmousedown = ((event) => {
          if (event.target.className !== "dialog_header") {
            return;
          }
          //获取鼠标在盒子中的位置
          let mouseX = event.clientX - vnode.offsetLeft;
          let mouseY = event.clientY - vnode.offsetTop;
          //绑定移动和停止函数
          document.onmousemove = ((event) => {
            let left, top;
            //获取新的鼠标位置对应下的盒子应该在的位置
            left = event.clientX - mouseX;
            top = event.clientY - mouseY;
            //获取div在页面中X轴的最小最大位置
            let minX = vnode.offsetWidth / 2;
            let maxX = window.innerWidth - (vnode.offsetWidth / 2);
            if (left <= minX) {
              left = minX;
            } else if (left >= maxX) {
              left = maxX;
            }
            //获取div在页面中Y轴的最大最小位置
            let minY = vnode.offsetHeight / 2;
            let maxY = window.innerHeight - (vnode.offsetHeight / 2);
            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%";
        });
     }
   }
}

7 代码解析

  1. 给弹窗绑定onmousedown事件,获取到鼠标在弹窗中的位置(以弹窗左上角为原点)。
  2. document绑定onmousemove事件,获取当前的鼠标位置,当前鼠标位置减去鼠标在弹窗的相当位置即可得到此时弹窗应该处于的位置。然后在通过style设置弹窗的位置。
  3. 鼠标松开解绑document的鼠标事件。

注意点:

  1. 弹窗要一直在页面可视区移动,最大的移动距离就是可视区的宽高减去盒子本身的宽高
    window.innerHeight - vnode.offsetHeight / 2;
    `window.innerWidth - vnode.offsetWidth / 2;
  2. 只有弹窗标题才能拖拽,所以判断非标题部分直接return
  3. 浏览器窗口大小改变会影响弹窗的位置,监听改变浏览器窗口改变把弹窗居中。

8. 使用

8-1. 单独引用

  1. 下载dialogDrag.vuedialogDrag.vue
  2. 单独组件使用
<v-drag
  :dialog-show.sync="dialogShow"
  dialog-width="700px"
  body-height="500px"
  :confirm-loading="loading"
  @confirm="dialogShow = false"
  @cancel="dialogShow = false"
>
  <div>test</div>
</v-drag>
import vDrag from "/dialogDrag";
export default {
  name: "test",
  components: {
    vDrag,
  },
  data() {
    return {
      dialogShow: false,
      loading: false
    }
  }
}

9. 脚手架v-cli全局引入

  1. src目录下新建components目录,下载dialogDrag.vue到此目录下。
  2. components目录下新建index.js
import VueDrag form ./vueDrag.vue
export default function install(Vue) {
    Vue.component("app-drag", VueDrag);
}
  1. main.js中加入代码
import appComponents from "./components/index.js";
Vue.use(appComponents);
  1. 页面中使用<app-drag></app-drag>

结语

目前我正在做的项目中对弹窗还有放大和缩小的需求,我参考layer-ui做了一个类似的组件,实现原理与当前这个组件保持一致。后续优化好了再发出来。

上一篇 下一篇

猜你喜欢

热点阅读