实现弹框浮动框在可视区域拖拽

649 阅读1分钟

前言

记得以前同事写过一个拖拽(我也写个菜鸟级别的吧),最近我也遇到这个业务,大概的整理一个最基础的demo,而不做更多的功能,方便不同业务手动扩展(就是懒,但是不能说!)

使用说明

好 好像都有备注说明了, ...

坑,总得说一下吧

1、slot="header"的头部作为鼠标拖拽部分,思考以下怎么拿到这个dom ?

1、插槽可不能绑定事件!
2、是不是想到在这个插槽外嵌套一个标签?你试试就知道了,这是个坑,不是说事件不起作用,而是如果改插槽里面内容覆盖的地方,事件也被“覆盖”,你点不到这个事件,你能点到就恐怖了!细品...
3、那解决方式?答案时肯定有撒,this.$slots.header[0].elm获取插槽dom,这时候如果你想了解更透彻,看看1.x和2.x+之间插槽舍弃了些什么。

2、什么时候为(this.$slots.header[0].elm)绑定事件,什么时候又给document绑定事件

当鼠标按下时,为this.$slots.header[0].elm绑定事件,目的获取鼠标在this.$slots.header[0].elm的坐标
(注:this.option.drapHeader = this.$slots.header[0].elm)
this.addEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
鼠标滑动时,时给document绑定事件,目的时获取鼠标在可视区域的坐标
this.addEvent(document, 'mousemove', this.mouseMove)

先上图,看看效果

来,实在点,说再多不如上代码实用

demo.vue
<template>
    <div class="drag-wrap">
        <draggable :width="500" :bottom="20" :right="20" :offset="[100,0,0,200]">
          <template slot="header">
            <div class="drag-handle">按下拖动</div>
          </template>
          <template slot="body">
            <div class="drag-content">
              这是内容
            </div>
          </template>
        </draggable>
    </div>
</template>
<script>
    import Draggable from './component/Draggable'
    export default {
        ...
        components: {
            Draggable
        }
        ...
    }
</script>
<style>
    .drag-wrap{
        .drag-handle{
          height: 40px;
          line-height: 40px;
          background: #ccc;
          cursor: all-scroll;
        }
        .drag-content{
          height: 300px;
          width: 100%;
          background: #eee;
        }
    }
</style>

拖拽组件 Draggable.vue

<template>
  <div ref="draggableWrap" :style="`width:${width}px`" class="draggable-wrap">
    <slot name="header"></slot>
    <slot name="body"></slot>
  </div>
</template>
<script>
export default {
  name: 'draggable',
  props: {
    // 只支持以下组合:top/left、top/right、bottom/left、bottom/right。注意top/bottom、left/right不可同时传递
    top: {
      type: Number,
      default: null
    },
    bottom: {
      type: Number,
      default: null
    },
    left: {
      type: Number,
      default: null
    },
    right: {
      type: Number,
      default: null
    },
    center: { // 显示在中心
      type: Boolean,
      default: false
    },
    width: {
      type: Number,
      default: 500
    },
    isDrag: { // 是否开启拖动
      type: Boolean,
      default: true
    },
    overflow: { // 是否超出屏幕,默认否,配合offset使用
      type: Boolean,
      default: false
    },
    offset: {
      type: Array,
      default: () => { // 距离屏幕边距,分别代表:top/right/bottom/left(px)
        return [0, 0, 0, 0]
      }
    }
  },
  data () {
    return {
      option: {
        drapDom: null, // 拖动元素
        drapHeader: null, // 拖动元素插槽的元素
        dragHeight: null, // 拖动元素的高度
        dragWidth: null,
        winWidth: null, // 浏览器窗口宽度
        winHeight: null,
        mouseX: 0, // 鼠标按下时相对拖动元素的left坐标
        mouseY: 0 // 鼠标按下时相对拖动元素的top坐标
      }
    }
  },
  mounted () {
    this.option.drapHeader = this.$slots.header[0].elm
    this.initData()
    window.addEventListener('resize', this.initData)
  },
  destroyed () {
    // window.removeEventListener('resize', this.initData)
    this.removeEvent(window, 'resize', this.initData)
    this.removeEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
  },
  methods: {
    // 初始化信息
    initData () {
      let drapDom = this.$refs.draggableWrap
      this.option.drapDom = drapDom
      this.option.dragHeight = drapDom.offsetHeight || 0
      this.option.dragWidth = drapDom.offsetWidth || 0
      this.option.winHeight = window.innerHeight || 0
      this.option.winWidth = window.innerWidth || 0
      this.initPosition()
    },
    // 初始化定位
    initPosition () {
      if (!this.center) {
        if (this.bottom !== null) {
          this.option.drapDom.style.top = this.option.winHeight - this.option.dragHeight - this.bottom + 'px'
        }
        if (this.right !== null) {
          this.option.drapDom.style.left = this.option.winWidth - this.option.dragWidth - this.right + 'px'
        }
        if (this.top !== null) {
          this.option.drapDom.style.top = this.top + 'px'
        }
        if (this.left !== null) {
          this.option.drapDom.style.left = this.left + 'px'
        }
      } else {
        this.option.drapDom.style.top = this.option.winHeight / 2 - this.option.dragHeight / 2 + 'px'
        this.option.drapDom.style.left = this.option.winWidth / 2 - this.option.dragWidth / 2 + 'px'
      }

      if (this.isDrag) {
        this.addEvent(this.option.drapHeader, 'mousedown', this.mouseDown)
      }
    },
    // 鼠标按下
    mouseDown (e) {
      this.option.mouseX = e.offsetX
      this.option.mouseY = e.offsetY
      this.addEvent(document, 'mousemove', this.mouseMove)
      this.addEvent(document, 'mouseup', this.mouseUp)
    },
    // 鼠标松开,移除事件
    mouseUp (e) {
      this.removeEvent(document, 'mousemove', this.mouseMove)
    },
    // 鼠标移动
    mouseMove (e) {
      let ny = e.pageY - this.option.mouseY
      let nx = e.pageX - this.option.mouseX
      if (this.overflow) {
        this.option.drapDom.style.top = ny + 'px'
        this.option.drapDom.style.left = nx + 'px'
      } else {
        let maxY = ny >= this.option.winHeight - this.option.dragHeight - this.offset[2] ? this.option.winHeight - this.option.dragHeight - this.offset[2] : ny
        let maxX = nx >= this.option.winWidth - this.option.dragWidth - this.offset[1] ? this.option.winWidth - this.option.dragWidth - this.offset[1] : nx
        this.option.drapDom.style.top = ny <= this.offset[0] ? this.offset[0] : maxY + 'px'
        this.option.drapDom.style.left = nx <= this.offset[3] ? this.offset[3] : maxX + 'px'
      }
    },
    // 绑定事件
    addEvent (el, event, handler) {
      if (!el) {
        return
      }
      if (el.attachEvent) {
        el.attachEvent('on' + event, handler)
      } else if (el.addEventListener) {
        el.addEventListener(event, handler, true)
      } else {
        el['on' + event] = handler
      }
    },
    // 移除事件
    removeEvent (el, event, handler) {
      if (!el) {
        return
      }
      if (el.detachEvent) {
        el.detachEvent('on' + event, handler)
      } else if (el.removeEventListener) {
        el.removeEventListener(event, handler, true)
      } else {
        el['on' + event] = null
      }
    }
  }
}
</script>
<style>
  .draggable-wrap{
    position: fixed;
    top: 20px;
    left: 20px;
  }
</style>