移动端悬浮按钮

<template>
  <div
    class="suspend_ball_wrapper"
    :style="{
      position: dragableRange === 'window' ? 'fixed' : 'absolute',
      ...position,
    }"
  >
    <div
      v-if="dragable"
      class="suspend_ball"
      @touchstart="touchstart"
      @touchmove="touchmove"
      @touchend="touchend"
    >
      <div class="slots" :class="[slotsDirectionData]">
        <slot />
      </div>
    </div>
    <div v-else class="suspend_ball">
      <div class="slots" :class="[slotsDirectionData]">
        <slot />
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      left: 0,
      top: 0,
      touchType: 0, //触摸类型 1拖动悬浮球 0点击悬浮球
      indentTimer: null,
      indentNearEdgeTimer: null,
      slotsDirectionData: "right",
    };
  },
  //   ## 属性
  // dragableRange: 拖动范围。parent 父级 window 可视窗口
  // dragable: 是否可拖动。默认 true
  // position:初始位置。[object Object] top、left、right、bottom
  // indent:是否需要缩进。默认 false
  // indentDelayTime:延时缩进。单位:ms, 为 0 则不缩进
  // indentDistance:缩进距离。单位:px
  // needNearEdge:拖动悬浮球后是否需要贴边。默认:false
  // nearEdgeTransition: 贴边过渡动画,transition 属性值。默认:'all ease 0.3s'
  // nearEdgeDirection:拖动悬浮球后贴边方向。默认贴边方向为距离最近的方向。
  // indentNearEdge:悬浮球贴边后是否需要缩进(此时缩进方向为贴边的方向)。默认 false
  // indentNearEdgeDelay: 悬浮球贴边后延时缩进。单位:ms,默认 1000,为 0 则不延时
  props: {
    dragable: {
      type: Boolean,
      default: true,
    },
    dragableRange: {
      type: String,
      default: "parent",
    },
    position: {
      type: Object,
      default: function () {
        return {
          left: 0,
          top: "50%",
        };
      },
    },
    indent: {
      type: Boolean,
      default: false,
    },
    indentDelayTime: {
      type: Number,
      default: 1000,
    },
    indentDistance: {
      type: Number,
      default: 30,
    },
    indentNearEdge: {
      type: Boolean,
      default: false,
    },
    needNearEdge: {
      type: Boolean,
      default: false,
    },
    nearEdgeTransition: {
      type: String,
      default: "all ease 0.3s",
    },
    nearEdgeDirection: String,
    indentNearEdgeDelay: {
      type: Number,
      default: 1000,
    },
    slotsDirection: {
      type: String,
      default: "right",
    },
  },
  created() {
    this.slotsDirection && (this.slotsDirectionData = this.slotsDirection);
  },
  mounted() {
    if (this.indent && this.indentDelayTime) {
      this.setIndent();
    }
  },
  methods: {
    touchstart(e) {
      const el = this.$el;

      e.preventDefault();
      //触摸类型 1拖动悬浮球 0点击悬浮球
      this.touchType = 0;
      this.$el.style.transition = "none";
      this.clearAllTimeout();

      //手指按下时的坐标
      const starX = e.touches[0].clientX;
      const starY = e.touches[0].clientY;

      this.starX = starX;
      this.starY = starY;

      //手指相对元素本身位置
      this.distanceX = starX - el.offsetLeft;
      this.distanceY = starY - el.offsetTop;
    },
    touchmove(e) {
      //手指移动到的坐标
      const moveX = e.touches[0].clientX;
      const moveY = e.touches[0].clientY;

      //手指移动距离
      const moveXDistance = moveX - this.starX;
      const moveYDistance = moveY - this.starY;

      //设置位置
      let left = moveX - this.distanceX;
      let top = moveY - this.distanceY;

      //处理点击产生的细微移动
      if (Math.abs(moveXDistance) > 15 || Math.abs(moveYDistance) > 15) {
        this.touchType = 1;
      }

      this.validMove(left, top);
    },
    touchend() {
      if (this.touchType === 0) {
        this.$emit("touchFunc");
      }

      //是否需要缩进
      if (this.needNearEdge) {
        const {
          self_offsetWidth,
          self_offsetHeight,
          parent_offsetWidth,
          parent_offsetHeight,
        } = this.getSelfAndParentOffset();

        const obj = {
          left: parseFloat(this.$el.style.left),
          top: parseFloat(this.$el.style.top),
        };

        obj.right = parent_offsetWidth - obj.left - self_offsetWidth;
        obj.bottom = parent_offsetHeight - obj.top - self_offsetHeight;

        //各方向中最小值
        let minKey = "";

        //计算贴边方向
        if (this.nearEdgeDirection) {
          minKey = this.nearEdgeDirection;
        } else {
          let min = Math.min();
          for (const key in obj) {
            const val = parseFloat(obj[key]);
            if (val < min) {
              min = val;
              minKey = key;
            }
          }
        }

        //设置贴边过渡
        if (this.nearEdgeTransition) {
          this.$el.style.transition = this.nearEdgeTransition;
        }

        //设置贴边
        this.$el.style[minKey] = 0;

        //设置贴边后缩进
        if (this.indentNearEdge) {
          if (this.indentNearEdgeDelay !== 0) {
            clearTimeout(this.indentNearEdgeTimer);

            this.indentNearEdgeTimer = setTimeout(() => {
              this.$el.style[minKey] = -this.indentDistance + "px";
              this.setOtherDirectionVal(minKey, this.indentDistance);
            }, this.indentNearEdgeDelay);
          } else {
            this.$el.style[minKey] = -this.indentDistance + "px";
          }
        }

        this.setOtherDirectionVal(minKey);
      }
    },

    //移动
    validMove(left, top) {
      const {
        self_offsetWidth,
        self_offsetHeight,
        parent_offsetWidth,
        parent_offsetHeight,
      } = this.getSelfAndParentOffset();

      if (left + self_offsetWidth > parent_offsetWidth) {
        left = parent_offsetWidth - self_offsetWidth;
      }
      if (top + self_offsetHeight > parent_offsetHeight) {
        top = parent_offsetHeight - self_offsetHeight;
      }
      if (left <= 0) {
        left = 0;
      }
      if (top <= 0) {
        top = 0;
      }

      this.$el.style.left = left + "px";
      this.$el.style.top = top + "px";
      this.left = left;
      this.top = top;
      this.right = "auto";
      this.bottom = "auto";
    },

    //设置缩进
    setIndent(delayTime) {
      clearTimeout(this.indentTimer);
      this.indentTimer = setTimeout(() => {
        let direction;
        let el = this.$el;
        const positionObj = this.getPositionComputedStyle();
        for (const key in positionObj) {
          if (parseFloat(positionObj[key]) === 0) {
            direction = key;
          }
        }

        el.style[direction] = el.style[direction] - this.indentDistance + "px";
      }, delayTime || this.indentDelayTime);
    },

    //设置其他方向position值
    setOtherDirectionVal(minKey, indentDistance = 0) {
      const {
        parent_offsetWidth,
        parent_offsetHeight,
        self_offsetWidth,
        self_offsetHeight,
      } = this.getSelfAndParentOffset();

      let slotsDirection = "";

      switch (minKey) {
        case "left":
          this.$el.style["right"] =
            parent_offsetWidth - self_offsetWidth + indentDistance + "px";
          slotsDirection = "right";
          break;
        case "right":
          this.$el.style["left"] =
            parent_offsetWidth - self_offsetWidth + indentDistance + "px";
          slotsDirection = "left";
          break;
        case "top":
          this.$el.style["bottom"] =
            parent_offsetHeight - self_offsetHeight + indentDistance + "px";
          slotsDirection = "bottom";
          break;
        case "bottom":
          this.$el.style["top"] =
            parent_offsetHeight - self_offsetHeight + indentDistance + "px";
          slotsDirection = "top";
          break;
      }

      //设置插槽方向(未设置slotsDirection则自动判断)
      if (!this.slotsDirection) {
        this.slotsDirectionData = slotsDirection;
      }
    },

    //计算offset值
    getSelfAndParentOffset() {
      const ifwindowRange = this.dragableRange === "window";
      const parent_offsetWidth = !ifwindowRange
        ? this.$el.parentNode.offsetWidth
        : window.innerWidth;
      const parent_offsetHeight = !ifwindowRange
        ? this.$el.parentNode.offsetHeight
        : window.innerHeight;
      const self_offsetWidth = this.$el.offsetWidth;
      const self_offsetHeight = this.$el.offsetHeight;
      return {
        parent_offsetWidth,
        parent_offsetHeight,
        self_offsetWidth,
        self_offsetHeight,
      };
    },

    getPositionComputedStyle() {
      let el = this.$el;
      let style = getComputedStyle(el);
      const positionObj = {
        top: style.top,
        bottom: style.bottom,
        left: style.left,
        right: style.right,
      };
      return positionObj;
    },

    clearAllTimeout() {
      clearTimeout(this.indentTimer);
      clearTimeout(this.indentNearEdgeTimer);
    },
  },
};
</script>
<style lang="less" scoped>
.suspend_ball_wrapper {
  position: absolute;
  // top: 0;
  // left: 0;
  width: 60px;
  height: 60px;
  z-index: 1000;
  .suspend_ball {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-size: cover;
    // background-image: url("https://img0.baidu.com/it/u=272572278,3446974957&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500");
    .slots {
      position: absolute;
      width: fit-content;
      height: 100%;
      &.right {
        left: calc(100% + 10px);
      }
      &.left {
        right: calc(100% + 10px);
      }
      &.top {
        bottom: calc(100% + 10px);
      }
      &.bottom {
        top: calc(100% + 10px);
      }
    }
  }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

碑无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值