教你实现一个悬浮可拖动并在滑动页面时会自动收缩的vue侧边组件按钮

1,995 阅读3分钟

一、前言

实现一个悬浮可拖动且可自定义的一个侧边按钮,在实际的业务开发中可以根据业务需要进行调整

效果图

最终实现的效果,在移动端收缩时可以加点延时判断增加一下最终的流畅性

效果图.gif

二、创建组件

创建一个div动态赋值高度、宽度等属性,内部包裹元素我这里用的是一张图片,实际可以根据需要展示不同的内容。

<div 
    @click="onBtnClicked"
    ref="floatButton"
    class="float-info"
    :style="{'width': itemWidth + 'px', 'height': itemHeight + 'px', 'left': left + 'px', 'top': top + 'px'}"
>
  <img src="../assets/imgs/return-up-page.png" @click="XXX()">
</div>

定义组件样式设置悬浮层级等

.float-info{
  box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.1);
  transition: all 0.3s;
  position: fixed;
  bottom: 50px;
  right: 30px;
  width: 55px;
  height: 55px;
  display: flex;
  flex-flow: column;
  justify-content: center;
  align-items: center;
  z-index: 999;
  background: #1E69EB;
  border-radius: 50%;
  cursor: pointer;
  img{
    width: 35px;
    height: 35px;
  }
}

data对象内定义属性

data() {
  return {
    clientWidth: 0,      // 页面宽度
    clientHeight: 0,     // 页面高度
    timer: null,
    currentTop: 0,
    left: 0,
    top: 0,

    oldScrollTop: 0,  //记录上一次滚动结束后的滚动距离
    scrollTop: 0,     // 记录当前的滚动距离
  }
},

props接收父级传来的参数

props: {
  itemWidth: {            // 按钮宽度
    type: Number,
    default: 55
  },
  itemHeight: {           // 按钮高度
    type: Number,
    default: 55
  },
  gapWidth: {             // 距离左右两边距离
    type: Number,
    default: 15
  },
  coefficientHeight: {    // 从上到下距离比例
    type: Number,
    default: 0.55
  }
},

created获取组件的初始化位置:首先获得页面宽度并减去按钮宽度及距离页面两侧的距离得到按钮的初始宽度位置,再通过页面高度*页面上下的距离比例得到组件的高度位置

created() {
  this.clientWidth = document.documentElement.clientWidth
  this.clientHeight = document.documentElement.clientHeight
  this.left = this.clientWidth - this.itemWidth - this.gapWidth
  this.top = this.clientHeight * this.coefficientHeight
},

三、addEventListener => touchstart touchmove touchend

获取组件Dom并通过addEventListener为该元素添加触摸事件touchstart touchmove touchend

touchstart事件:当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。

touchmove事件:当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动。

touchend事件:当手指从屏幕上离开的时候触发。

mounted() {
  this.$nextTick(() => {
    const floatButton = this.$refs.floatButton
    floatButton.addEventListener("touchstart", () => {
      floatButton.style.transition = 'none'
    })

    // 在拖拽的过程中,组件应该跟随手指的移动而移动。
    floatButton.addEventListener("touchmove", (e) => {
      if (e.targetTouches.length === 1) {         // 一根手指
        document.body.addEventListener('touchmove', this.bodyScroll, { passive: false });  //禁止页面滑动
        let touch = e.targetTouches[0]
        this.left = touch.clientX - 20
        this.top = touch.clientY - 25
      }
    })

    // 拖拽结束以后,重新调整组件的位置并重新设置过度动画。
    floatButton.addEventListener("touchend", () => {
      document.body.removeEventListener('touchmove', this.bodyScroll, { passive: false });  //解除页面禁止滑动
      floatButton.style.transition = 'all 0.3s'
      if(this.left > document.documentElement.clientWidth / 2) {
        this.left = document.documentElement.clientWidth - 70;
      }else{
        this.left = 15
      }
    })
  })
  // 监听scroll事件获取滚动距离
  this.handleScroll();
},

四、监听组件滑动

handleScroll() {
  window.addEventListener('scroll', () => {
    this.scrollTop = window.scrollY;
  })
},

开始滑动

当组件开始滑动时判断上次滑动距离是否等于监听到的Old值,等于则说明开始滑动,这时我们可以将组件距离侧边的距离减去组件自身的一半宽度+组件默认距离侧边的距离,这样就可以实现在滑动组件时组件收缩到页面内侧的一个效果。

watch: {
    scrollTop(newValue, oldValue){}
}
......
if(this.oldScrollTop == oldValue) { //每次滚动开始时oldScrollTop与oldValue相等
  //console.log('滚动开始');
  if (this.left === 15){
    this.left = this.left - 40
  }else {
    this.left = this.left + 40
  }
}

结束滑动

结束滑动时判断滑动距离是否等于页面水平滚动的像素数,等于则代表了停止滑动,这时判断一下当前组件在页面左侧还是右侧并调整相关的距离参数,这样就实现了停止拖动按钮时按钮自动回缩至侧边的效果。

watch: {
  scrollTop(newValue, oldValue) {
    setTimeout(() => {
      if(newValue == window.scrollY) { //延时执行后当newValue等于window.scrollY,代表滚动结束
        //console.log('滚动结束');
        if (this.left === 15){  //按钮在页面左侧
          this.left = this.left + 40
        }else if (this.left < 15){
          this.left = 15
        }else {
          this.left = this.left - 40
        }
        this.oldScrollTop = newValue; //每次滚动结束后都要给oldScrollTop赋值
      };
    }, 20); //必须使用延时器,否则每次newValue和window.scrollY都相等,无法判断,20ms刚好大于watch的侦听周期,故延时20ms
  }
},

写在最后

文章如有不足之处请指出,我们一起学习交流,万分感谢~~~