pc端移动端共同实现滑动效果-vue

本文介绍如何使用Vue.js和JavaScript在PC端和移动端实现滑动效果。通过事件处理(mousedown/mouseup/touchstart/touchend)、变量管理和功能代码,结合CSS实现平滑的滑动交互。在移动端,需要处理touch事件并计算触摸点的变化。同时,文章提供了一个完整的JS代码示例,以及效果展示。
摘要由CSDN通过智能技术生成

原创内容,引用请注明原处

<template>
  <div class="template-swiper-move">
    <ul
      ref="swiper"
      class="swiper-list"
      :style="{
        transform: `translateX(${moveX}px)`,
        transitionDuration: `${transDuration}ms`,
      }"
      @mousedown="handlerDragStart"
      @mouseup="handlerMouseUp"
      @touchstart="handlerTouchStart"
      @touchmove="handlerTouchMove"
      @touchend="handlerTouchEnd"
    >
      <slot></slot>
    </ul>
  </div>
</template>

模板使用的transform来滑动,注意:使用 transform 和 position 滑动不一样,获取到的dom数据会有不同,例如offsetLeft 在 transform下是无法获得的,同理position下可以获取到offsetLeft

1.事件处理

  1. pc端使用mousedown 和 mouseup
  2. 移动端使用touch的整个流程

2.使用变量

props: {
    dataList: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      cellList: [], // 记录子集
      moveX: 0, // 移动量
      widthGap: 0, // 实际可滚动距离(左边界为0, widthGap为右边界)
      oldMoveX: 0, // 上一次滑动的位置
      oldx: 0, // 当前摁下的鼠标位置
      transDuration: 0, // 设置动画时长
      cellLeft: [], // 记录所有cell 的offsetLeft 值
    }
  },

3. 功能代码

handlerDragStart(event) {
  const swiper = this.$refs.swiper
  const scrollWidth = swiper.scrollWidth
  this.widthGap = scrollWidth - this.$refs.swiper.clientWidth// 可滚动范围
  this.oldMoveX = this.moveX // oldMoveX 是上一次滑动留下的距离
  this.oldx = event.layerX
  window.addEventListener('mousemove', this.handlerDragMove)
},

鼠标摁下,求得滚动范围,以及上一次滚动距离,当前鼠标摁下的距离,并且像window 添加鼠标滑动事件

handlerDragMove(event) {
   const slideGap = event.layerX - this.oldx
   this.moveX = this.oldMoveX + slideGap
},

鼠标滑动记录,当前滑动的距离,并且给偏移量moveX 赋值,

  1. 用鼠标滑动停止的距离layerX 减去 鼠标摁下时的即时位置, 获取鼠标实际滑动的距离,
  2. 实际滑动距离,加上上次移动的距离,等于最终滑动的偏移量
handlerMouseUp() {
  window.removeEventListener('mousemove', this.handlerDragMove)
  this.slideEnd()
},
// 滑动后结束处理
    slideEnd() {
      new Promise((resolve, reject) => {
        this.transDuration = 500
        resolve()
      }).then(() => {
        this.checkCenterCell()
        setTimeout(() => {
          this.transDuration = 0
        }, this.transDuration)
      })
    },

    // 查看当前,最近的cell
    checkCenterCell() {
      // moveX代表了显示容器的左侧实时位置, area 代表右侧
      const area = this.$refs.swiper.clientWidth - this.moveX 
      const cell = this.cellList[0].elm.clientWidth
      // 比较显示容器里
      const left = this.cellLeft.reduce((prev, next) => {
        // 显示容器左侧到prev右侧,
        const a = prev + cell - Math.abs(this.moveX) 
		// 显示容器右侧到next左侧
        const b = area - next
        // b<0 a<0 排除掉离当前滑动距离太远的元素,否则会出现负大于正的情况
        if (b < 0) { 
          return prev
        } else if (a < 0) {
          return next
        } else if (Math.abs(a) > Math.abs(b)) { // 判断可用区域大小
          return prev
        } else {
          return next
        }
      })
      let index = 0
      this.cellLeft.forEach((v, i) => {
        if (v === left) {
          index = i
        }
      })
      const space = this.$refs.swiper.clientWidth - this.cellList[index].elm.clientWidth) / 2
      // 传递修改标志位
      this.$emit('changeActive', index)
      /* 
        1.第一种可能,划出左右最大范围
        2.第二种可能,在正常区域内滑动
      */
      // 当移动,划出最大显示范围时,回弹至正常区域
      if (this.moveX > 0) {
        this.moveX = space - this.cellList[0].elm.offsetLeft
      } else if (this.moveX < -this.widthGap) {
        this.moveX =
          space - this.cellList[this.cellList.length - 1].elm.offsetLeft
      }
      // 遍历当前子元素集,谁的距离,离当前滑动距离最近,居中回弹,获取其下标
      this.moveX = space - this.cellList[index].elm.offsetLeft
    },
  1. slideEnd()方法是添加一个动画效果, 滑动时不需要执行时间,所以duration设置为0 但当元素回弹时,需要动画效果,这是通过异步设置一个执行时间。
  2. checkCenterCell() 寻找一个最近的需要回弹的元素,通过reduce循环,判断出当前有效距离最大的元素,确定元素的下标,进行回弹
  3. checkCenterCell() 中的space常量,是保证子元素在页面居中,所以需要左右边距一致,这是就需要去计算他的宽度
    距离计算原理图

4.css

<style lang="scss" scoped>
.template-swiper-move {
  overflow: hidden;
  .swiper-list {
    transition-property: transform;
    transition-timing-function: ease;
    display: flex;
    -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Chrome/Safari/Opera */
    -khtml-user-select: none; /* Konqueror */
    -moz-user-select: none; /* Firefox */
    -ms-user-select: none; /* Internet Explorer/Edge */
    user-select: none;
  }
}
</style>

页面是通过slot接受子元素,子元素样式由各页面自定

5.移动端

移动端与pc代码主流程一致,touchmove对应 mousemove, 移动端获取event的距离有所不同,需要event.targetTouches[0]

6.完整js代码

<script>
export default {
  props: {
    isCircle: {
      type: Boolean,
      default: false,
    },
    dataList: {
      type: Array,
      default: () => [],
    },
    active: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      cellList: [], // 记录子集
      moveX: 0, // 移动量
      widthGap: 0, // 实际可滚动距离(左边界为0, widthGap为右边界)
      oldMoveX: 0, // 上一次滑动的位置
      oldx: 0, // 当前摁下的鼠标位置
      transDuration: 0, // 设置动画时长
      cellLeft: [], // 记录所有cell 的offsetLeft 值
    }
  },
  watch: {
    dataList: {
      handler(val) {
        if (val.length) {
          this.$nextTick(() => {
            this.handelrReady()
          })
        }
      },
      immediate: true,
    },
  },
  mounted() {
    window.addEventListener('resize', this.checkCenterCell)
    // 隐藏状态下获取数据都是0, 当尺寸变化时,重新获取一次
    window.addEventListener('resize', this.handelrReady)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.checkCenterCell)
    window.removeEventListener('resize', this.handelrReady)
  },
  methods: {
    handelrReady() {
      // 获取slot传递过来的li元素
      this.cellList = this.$slots.default
      // this.cellLeft = this.$slots.default.map(
      //   (v) => v.elm.offsetLeft - v.elm.clientWidth / 2
      // )
      this.cellLeft = this.$slots.default.map((v) => v.elm.offsetLeft)
      this.checkCenterCell()
    },
    // 拖拽
    handlerDragStart(event) {
      const swiper = this.$refs.swiper
      const scrollWidth = swiper.scrollWidth
      this.widthGap = scrollWidth - this.$refs.swiper.clientWidth // 可滚动范围
      this.oldMoveX = this.moveX // oldMoveX 是上一次滑动留下的距离
      this.oldx = event.layerX
      window.addEventListener('mousemove', this.handlerDragMove)
    },
    handlerDragMove(event) {
      const slideGap = event.layerX - this.oldx
      this.moveX = this.oldMoveX + slideGap
    },
    handlerMouseUp() {
      window.removeEventListener('mousemove', this.handlerDragMove)
      this.slideEnd()
    },

    handlerTouchStart(event) {
      const swiper = this.$refs.swiper
      const scrollWidth = swiper.scrollWidth
      this.widthGap = scrollWidth - this.$refs.swiper.clientWidth // 可滚动范围
      this.oldMoveX = this.moveX // offsetleft 是上一次滑动留下的距离
      this.oldx = event.targetTouches[0].screenX // 当前点击的位置
    },
    handlerTouchMove(event) {
      const slideGap = event.targetTouches[0].screenX - this.oldx
      this.moveX = this.oldMoveX + slideGap
    },
    handlerTouchEnd() {
      const obj = {}
      for (const attr in this.$refs.swiper) {
        obj[attr] = this.$refs.swiper[attr]
      }
      this.slideEnd()
    },

    // 滑动后结束处理
    slideEnd() {
      new Promise((resolve, reject) => {
        this.transDuration = 500
        resolve()
      }).then(() => {
        this.checkCenterCell()
        setTimeout(() => {
          this.transDuration = 0
        }, this.transDuration)
      })
    },

    // 查看当前,最近的cell
    checkCenterCell() {
      const area = this.$refs.swiper.clientWidth - this.moveX // moveX代表了显示容器的左侧实时位置, area 代表右侧
      const cell = this.cellList[0].elm.clientWidth
      // 比较显示容器里
      const left = this.cellLeft.reduce((prev, next) => {
        const a = prev + cell - Math.abs(this.moveX) // 显示容器左侧到prev右侧,
        const b = area - next
        if (b < 0) { // b<0 a<0 排除掉离当前滑动距离太远的元素,否则会出现负大于正的情况
          return prev
        } else if (a < 0) {
          return next
        } else if (Math.abs(a) > Math.abs(b)) { // 判断可用区域
          return prev
        } else {
          return next
        }
      })
      let index = 0
      this.cellLeft.forEach((v, i) => {
        if (v === left) {
          index = i
        }
      })
      const space =
        (this.$refs.swiper.clientWidth - this.cellList[index].elm.clientWidth) / 2
      // 传递修改标志位
      this.$emit('changeActive', index)
      /* 
        1.第一种可能,划出左右最大范围
        2.第二种可能,在正常区域内滑动
      */
      // 当移动,划出最大显示范围时,回弹至正常区域
      if (this.moveX > 0) {
        this.moveX = space - this.cellList[0].elm.offsetLeft
      } else if (this.moveX < -this.widthGap) {
        this.moveX =
          space - this.cellList[this.cellList.length - 1].elm.offsetLeft
      }
      // 遍历当前子元素集,谁的距离,离当前滑动距离最近,居中回弹,获取其下标
      this.moveX = space - this.cellList[index].elm.offsetLeft
    },
  },
}
</script>

效果展示

左右滑动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值