封装一个悬浮按钮拖拽组件(vue2语法)

组件代码/components/float-bubble.vue

<template>
  <transition>
    <div
      ref="dragArea"
      class="drag-area"
      @touchstart.stop="handleTouchStart"
      @touchmove.prevent.stop="handleTouchMove($event)"
      @touchend.stop="handleTouchEnd"
      :style="{ left: `${left}px`, top: `${top}px`, width: `${slotWidth}px`, height: `${slotHeight}px` }"
      v-if="isShow"
    >
      <slot></slot>
    </div>
  </transition>
</template>

<script>
export default {
  props: {
    slotElemClass: {
      //slot包裹元素类名,必传,用以获取其宽高时使用,如<FloatBubble slotElemClass=".float-btn">...</FloatBubble>
      type: String,
      required: true,
    },
    isAdsorb: {
      //是否开启自动吸附侧边
      type: Boolean,
      default: true,
    },
    adsorbType: {
      //吸附侧边类型:right-仅吸附右边,left-仅吸附左边,double-自适应吸附两边
      type: String,
      default: 'double',
    },
    initTop: {
      //元素初始top值,不设置默认居中
      type: Number,
    },
    initLeft: {
      //元素初始left值,不设置默认100%,即在最右侧
      type: Number,
    },
  },
  data() {
    return {
      slotWidth: '', //插槽元素宽度,因使用了rem缩放,所以这里是动态计算得来的
      slotHeight: '', //插槽元素高度
      disdanceEdge: 0, //吸附后距离吸附边的间距,默认为0,即贴边吸附
      left: 0,
      top: 0,
      startToMove: false,
      isShow: true,
      timer: null,
      currentTop: null,
      clientW: document.documentElement.clientWidth, //视口宽
      clientH: document.documentElement.clientHeight, //视口高
    };
  },
  mounted() {
    this.slotWidth = document.querySelector(this.slotElemClass).clientWidth;
    this.slotHeight = document.querySelector(this.slotElemClass).clientHeight;
    this.left = this.initLeft || this.clientW - this.slotWidth - this.disdanceEdge; //初始化默认left为右贴边
    this.top = this.initTop || this.clientH / 2 - this.slotHeight / 2; //初始化默认top为居中
    this.bindScrollEvent();
  },
  beforeDestroy() {
    // 记得销毁一些全局的的事件
    this.removeScrollEvent();
  },
  methods: {
    handleTouchStart() {
      this.startToMove = true;
      this.$refs.dragArea.style.transition = 'none';
    },
    handleTouchMove(e) {
      const clientX = e.targetTouches[0].clientX; //手指相对视口的x
      const clientY = e.targetTouches[0].clientY; //手指相对视口的y
      const isInScreen = clientX <= this.clientW && clientX >= 0 && clientY <= this.clientH && clientY >= 0;
      if (this.startToMove && e.targetTouches.length === 1) {
        if (isInScreen) {
          this.left = clientX - this.slotWidth / 2;
          this.top = clientY - this.slotHeight / 2;
        }
      }
    },
    handleTouchEnd() {
      if (this.left < this.clientW / 2) {
        if (this.isAdsorb) {
          if (['double', 'left'].includes(this.adsorbType)) {
            this.left = this.disdanceEdge;
          } else {
            this.left = this.clientW - this.slotWidth - this.disdanceEdge;
          }
        }
        this.handleIconY();
      } else {
        if (this.isAdsorb) {
          if (['double', 'right'].includes(this.adsorbType)) {
            this.left = this.clientW - this.slotWidth - this.disdanceEdge;
          } else {
            this.left = this.disdanceEdge;
          }
        }
        this.handleIconY();
      }
      this.$refs.dragArea.style.transition = 'all .3s';
    },
    handleIconY() {
      if (this.top < 0) {
        this.top = this.disdanceEdge; //贴边距离
      } else if (this.top + this.slotHeight > this.clientH) {
        this.top = this.clientH - this.slotHeight - this.disdanceEdge;
      }
    },
    bindScrollEvent() {
      window.addEventListener('scroll', this.handleScrollStart);
    },
    removeScrollEvent() {
      window.removeEventListener('scroll', this.handleScrollStart);
    },
    handleScrollStart() {
      // this.isShow = false;
      this.timer && clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        this.handleScrollEnd();
      }, 300);
      this.currentTop = document.documentElement.scrollTop || document.body.scrollTop;
    },
    handleScrollEnd() {
      this.scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      // 判断是否停止滚动的条件
      if (this.scrollTop == this.currentTop) {
        this.isShow = true;
      }
    },
  },
};
</script>

<style scoped lang="less">
.drag-area {
  position: fixed;
  z-index: 800;
}
.v-enter {
  opacity: 1;
}
.v-leave-to {
  opacity: 0;
}
.v-enter-active,
.v-leave-active {
  transition: opacity 0.3s;
}
</style>

如何使用

import FloatBubble from '@/components/.../float-bubble.vue';
components: {
    FloatBubble,
 },

<FloatBubble slotElemClass=".float-btn">
      <div class="float-btn">按钮</div>
</FloatBubble>

posted @ 2024-07-30 10:22  huihuihero  阅读(257)  评论(0编辑  收藏  举报