vue2自由拖动组件,自动吸附

173 阅读2分钟

组件文件

某天突然有这么个需求,看到了个例子,拿来优化了下,可以完美在容器内部自由拖动并自动吸附, 部分参数和移动端拖动方法还需优化,PC端可用,记录一下

<template>
  <div
    ref="floatDrag"
    class="float-position"
    :style="{ left: left + 'px', top: top + 'px', zIndex: zIndex }"
    @touchmove.prevent
    @mousemove.prevent
    @mousedown="mouseDown"
    @mouseup="mouseUp"
  >
    <slot v-bind:canClick="canClick"></slot>
  </div>
</template>

<script>
export default {
  name: "DragBall",
  props: {
    distanceRight: {
      type: Number,
      default: 0,
    },
    distanceBottom: {
      type: Number,
      default: 100,
    },
    isScrollHidden: {
      type: Boolean,
      default: false,
    },
    isCanDraggable: {
      type: Boolean,
      default: true,
    },
    zIndex: {
      type: Number,
      default: 999,
    },
    mTop: {
      type: Number,
      default: 50,
    },
    positon: {
      type: String,
      default: "right",
    },
  },

  //data 域
  data() {
    return {
      containerWidth: null,
      containerHeight: null,
      left: 0,
      top: 0,
      timer: null,
      currentTop: 0,
      mousedownX: 0,
      mousedownY: 0,
      offsetX: 0,
      offsetY: 0,
      canClick: true,
    };
  },
  created() {},
  mounted() {
    this.isCanDraggable &&
      this.$nextTick(() => {
        this.floatDrag = this.$refs.floatDrag;
        // // 获取元素位置属性
        this.floatDragDom = this.floatDrag.getBoundingClientRect();
        this.containerWidth = this.$refs.floatDrag.parentElement.clientWidth;
        this.containerHeight = this.$refs.floatDrag.parentElement.clientHeight;
        // 计算父元素左上角相对于窗口的偏移量
        this.offsetX =
          this.$refs.floatDrag.parentElement.getBoundingClientRect().left;
        this.offsetY =
          this.$refs.floatDrag.parentElement.getBoundingClientRect().top;
        this.top = this.mTop;
        this.left = this.containerWidth - this.floatDragDom.width;
        this.initDraggable();
      });
    this.top =
      this.isScrollHidden &&
      window.addEventListener("scroll", this.handleScroll);
    window.addEventListener("resize", this.handleResize);
  },
  methods: {
    /**
     * 设置滚动监听
     * 设置滚动时隐藏悬浮按钮,停止时显示
     */
    handleScroll() {
      this.timer && clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        this.handleScrollEnd();
      }, 200);
      this.currentTop =
        document.documentElement.scrollTop || document.body.scrollTop;
      if (this.left > this.containerWidth / 2) {
        // 判断元素位置再左侧还是右侧
        this.left = this.containerWidth + this.floatDragDom.width;
      } else {
        this.left = -this.floatDragDom.width;
      }
    },
    /**
     * 滚动结束
     */
    handleScrollEnd() {
      let scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop;
      if (scrollTop === this.currentTop) {
        console.log(this.left);
        if (this.left > this.containerWidth / 2) {
          // 判断元素位置再左侧还是右侧
          this.left = this.containerWidth - this.floatDragDom.width;
        } else {
          this.left = 0;
        }
        clearTimeout(this.timer);
      }
    },
    /**
     * 窗口resize监听
     */
    handleResize() {
      this.containerWidth = document.documentElement.containerWidth;
      this.containerHeight = document.documentElement.containerHeight;
      this.checkDraggablePosition();
    },
    /**
     * 初始化draggable
     */
    initDraggable() {
      this.floatDrag.addEventListener("touchstart", this.toucheStart);
      this.floatDrag.addEventListener("touchmove", (e) => this.touchMove(e));
      this.floatDrag.addEventListener("touchend", this.touchEnd);
    },
    mouseDown(e) {
      const event = e || window.event;
      // 不是左键
      if (event.button !== 0) {
        return;
      }
      this.disabled = false;
      this.mousedownX = event.screenX;
      this.mousedownY = event.screenY;
      const that = this;
      let floatDragWidth = this.floatDragDom.width / 2;
      let floatDragHeight = this.floatDragDom.height / 2;
      if (event.preventDefault) {
        event.preventDefault();
      }
      this.canClick = false;

      this.floatDrag.style.transition = "none";
      console.log("containerHeight :>> ", this.containerHeight);
      document.onmousemove = function (e) {
        var event = e || window.event;
        that.left = event.clientX - floatDragWidth - that.offsetX;
        that.top = event.clientY - floatDragHeight - that.offsetY;
        if (that.left < 0) that.left = 0;
        if (that.top < 0) that.top = 0;
        if (that.left >= that.containerWidth - floatDragWidth * 2) {
          that.left = that.containerWidth - floatDragWidth * 2;
        }
        if (that.top >= that.containerHeight - floatDragHeight * 2) {
          console.log("that.top :>> ", that.top);
          that.top = that.containerHeight - floatDragHeight * 2;
        }
      };
    },
    mouseUp(e) {
      const event = e || window.event;
      //判断只是单纯的点击,没有拖拽
      if (
        this.mousedownY == event.screenY &&
        this.mousedownX == event.screenX
      ) {
        this.canClick = true;
        this.$emit("handlepaly");
      }
      document.onmousemove = null;
      this.checkDraggablePosition();
      this.floatDrag.style.transition = "all 0.3s";
      setTimeout(() => {
        this.canClick = true;
      }, 300);
    },
    toucheStart() {
      this.canClick = false;
      this.floatDrag.style.transition = "none";
    },
    touchMove(e) {
      this.canClick = true;
      if (e.targetTouches.length === 1) {
        // 单指拖动
        let touch = event.targetTouches[0];
        this.left = touch.clientX - this.floatDragDom.width / 2;
        this.top = touch.clientY - this.floatDragDom.height / 2;
      }
    },
    touchEnd() {
      if (!this.canClick) return; // 解决点击事件和touch事件冲突的问题
      this.floatDrag.style.transition = "all 0.3s";
      this.checkDraggablePosition();
    },
    /**
     * 判断元素显示位置
     * 在窗口改变和move end时调用
     */
    checkDraggablePosition() {
      // 默认靠右
      // if (this.left + this.floatDragDom.width / 2 >= this.containerWidth / 2) {
      //   // 判断位置是往左往右滑动
      this.left = this.containerWidth - this.floatDragDom.width;
      // } else {
      //   this.left = 0;
      // }
      if (this.top < 0) {
        // 判断是否超出屏幕上沿
        this.top = 0;
      }
      if (this.top + this.floatDragDom.height >= this.containerHeight) {
        // 判断是否超出屏幕下沿
        this.top = this.containerHeight - this.floatDragDom.height;
      }
    },
  },
  beforeDestroy() {
    window.removeEventListener("scroll", this.handleScroll);
    window.removeEventListener("resize", this.handleResize);
  },
};
</script>
<style>
/* html,
body {
  overflow: hidden;
} */
</style>
<style scoped lang="scss">
.float-position {
  position: absolute;
  z-index: 10003;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}
</style>

使用示例

容器container需要设置相对定位

<div class="container">
    <dragFreely style="cursor: pointer">
      <template v-slot:default="slotProps">
        <selectedListTag
          v-if="showSelectTag"
          :num="selectedNum"
          :canClick="slotProps.canClick"
        />
      </template>
    </dragFreely>
</div>

子组件需要在porps接受canClick并在点击事件响应前进行判断

function(){
    if(!this.canclick) return; 
    // 你的业务代码
}