vue实现移动端悬浮可拖拽按钮

需求:

  1. 按钮在页面侧边悬浮显示;
  2. 点击按钮可展开多个快捷方式按钮,从下向上展开。
  3. 长按按钮,则允许拖拽来改变按钮位置,按钮为非展开状态;
  4. 按钮移动结束,手指松开,计算距离左右两侧距离并自动移动至侧边显示;
  5. 移动至侧边后,根据具体左右两次位置判断改变展开样式;
  6. 处理移动到非可视区域时特殊情况。

效果展示:
在这里插入图片描述

具体实现:

<template>
  <div class="shortcut" @touchstart="touchstart($event)" @touchmove="touchMove($event)" @touchend="touchEnd($event)" v-if="isMobile">
    <div class="shortcut__container">
      <transition name="fade">
        <div class="shadow" v-if="showPopover" @click.stop="showPopover = false"></div>
      </transition>
      <transition name="sub-fade">
        <div :class="['shortcut__list', `${type}`]" v-if="showPopover">
          <div class="shortcut__list_item">
            <div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
            投资理财
          </div>
          <div class="shortcut__list_item">
            <div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
            我的资产
          </div>
          <div class="shortcut__list_item">
            <div class="icon-box"><img src="@/images/common/ic_question.png" alt=""></div>
            咨询我们
          </div>
        </div>
      </transition>
      <div class="shortcut__btn" :class="{ anim: showPopover }" @click.stop="handleBtn()">+</div>
    </div>
  </div>
</template>

<script>
const TIME = 50
export default {
  data () {
    return {
      isMobile: /Mobi|Android|iPhone/i.test(navigator.userAgent),
      showPopover: false,
      timeOutEvent: 0,
      longClick: 0,
      // 手指原始位置
      oldMousePos: {},
      // 元素原始位置
      oldNodePos: {},
      type: 'right',
    }
  },
  methods: {
    touchstart (ev) {
      // 定时器控制长按时间,超过{TIME}毫秒开始进行拖拽
      this.timeOutEvent = setTimeout(() => {
        this.longClick = 1
      }, TIME)
      const selectDom = ev.currentTarget
      const { pageX, pageY } = ev.touches[0] // 手指位置
      const { offsetLeft, offsetTop } = selectDom // 元素位置
      // 手指原始位置
      this.oldMousePos = {
        x: pageX,
        y: pageY,
      }
      // 元素原始位置
      this.oldNodePos = {
        x: offsetLeft,
        y: offsetTop,
      }
      this.handleMoving()
      selectDom.style.left = `${offsetLeft}px`
      selectDom.style.top = `${offsetTop}px`
    },
    touchMove (ev) {
      // 未达到{TIME}毫秒就移动则不触发长按,清空定时器
      clearTimeout(this.timeOutEvent)
      if (this.longClick === 1) {
        this.handleMoving()
        this.showPopover = false

        const selectDom = ev.currentTarget
        // x轴偏移量
        const lefts = this.oldMousePos.x - this.oldNodePos.x
        // y轴偏移量
        const tops = this.oldMousePos.y - this.oldNodePos.y
        const { pageX, pageY } = ev.touches[0] // 手指位置
        selectDom.style.left = `${pageX - lefts}px`
        selectDom.style.top = `${pageY - tops}px`
      }
    },
    touchEnd (ev) {
      // 清空定时器
      clearTimeout(this.timeOutEvent)
      if (this.longClick === 1) {
        this.longClick = 0
        const selectDom = ev.currentTarget
        const { innerWidth, innerHeight } = window
        const { offsetLeft, offsetTop } = selectDom
        selectDom.style.left = offsetLeft + 50 > innerWidth / 2 ? 'calc(100% - 55px)' : '15px'
        if (offsetTop < 150) {
          selectDom.style.top = '150px'
        } else if (offsetTop + 150 > innerHeight) {
          selectDom.style.top = `${innerHeight - 150}px`
        }
        this.type = offsetLeft + 50 > innerWidth / 2 ? 'right' : 'left'

        setTimeout(() => {
          document.body.style.overflow = 'auto'
          document.body.style.userSelect = 'auto'
        }, 1000)
      }
    },
    handleMoving () {
      // 禁止body滚动
      document.body.style.overflow = 'hidden'
      // 禁止body文本选中
      document.body.style.userSelect = 'none'
    },
    handleBtn () {
      this.showPopover = !this.showPopover
    }
  },
}
</script>

<style scoped lang="less">
.icon-box {
  background: #fff;
  width: .8rem;
  height: .8rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.shortcut {
  position: fixed;
  z-index: 9999;
  left: calc(100% - 55px);
  top: calc(100% - 150px);
  user-select: none;

  &__container {
    position: relative;
  }

  &__list {
    position: absolute;
    bottom: .8rem;
    z-index: 8;
    &_item {
      color: #fff;
      display: flex;
      flex-direction: row;
      align-items: center;
      white-space: nowrap;
      margin-bottom: .15rem;

      .icon-box {
        margin: 0 .1rem 0 0;

        img {
          width: 0.36rem;
          height: 0.36rem;
        }
      }
    }

    &.left {
      left: 0;
    }

    &.right {
      right: 0;
      .shortcut__list_item {
        flex-direction: row-reverse;
        .icon-box {
          margin: 0 0 0 .1rem;
        }
      }
    }
  }

  &__btn {
    background: #fff;
    width: .8rem;
    height: .8rem;
    border-radius: 50%;
    text-align: center;
    line-height: .7rem;
    color: #3356D9;
    font-size: .5rem;
    position: relative;
    z-index: 8;
    border: 1px solid #3356D9;
    transition: all .3s linear;

    &.anim {
      transform: rotate(135deg);
    }
  }
}
.shadow {
  width: 100%;
  max-width: 1024px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 1;
  margin: 0 auto;
}
.sub-fade-leave-active,.sub-fade-enter-active {
  transition: max-height 0.3s linear;
}
.sub-fade-enter,.sub-fade-leave-to {
  max-height: 0;
  overflow: hidden;
}
.sub-fade-enter-to,.sub-fade-leave {
  max-height: 2.56rem;
  overflow: hidden;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值