用 Vue.js 写一个层叠卡片滑动切换、卡牌动态滑动切换效果

vue-slide-card

Vue 层叠卡片滑动切换、卡牌动态滑动切换效果

效果展示

实现步骤

  • 给最外层容器固定高度(视口高度),绑定三个滑动事件
    hmtl:
<template>
  <div
    @touchstart="playerTouchStart"
    @touchmove="playerTouchMove"
    @touchend="playerTouchEnd"
    @mousedown="playerTouchStart"
    @mousemove="playerTouchMove"
    @mouseup="playerTouchEnd"
    class="container">

css:

.container {
  height: 100vh;
  position: relative;
  overflow: hidden;
  padding-top: 74px;
}
  • 通过记录开始触摸位置,滑动时判断滑动方向,比较麻烦的是,滑动时需要动态去计算当前变化的每一个卡片的位置,然后滑动结束时,使用 transition 来使卡片正确归位。
    js:
methods: {
    // 滑动开始
    playerTouchStart (ev) {
      ev = ev || event
      this.isClick = true
      // tounches类数组,等于1时表示此时有只有一只手指在触摸屏幕
      if (ev.touches.length === 1) {
        // 记录开始位置
        this.startY = ev.touches[0].clientY
        console.log('开始触摸-startY', this.startY)
      }
    },
    // 滑动中
    playerTouchMove (ev) {
      ev = ev || event
      this.isClick = false
      if (ev.touches.length === 1) {
        // 滑动时距离浏览器左侧实时距离
        this.moveY = ev.touches[0].clientY
        // 起始位置减去实时的滑动的距离,得到手指实时偏移距离
        this.disY = this.startY - this.moveY
        console.log('滑动-disY', this.disY)
        // 判断滑动方向
        if (this.disY < 0) {
        // 向下滑
          this.slideDirection = 0
          // 当前上一个变化
          if (this.cardArrs[this.currentIndex - 1]) {
            let item_0 = this.cardArrs[this.currentIndex - 1]
            item_0.translateY = -window.innerHeight - this.disY + 'px'
            item_0.transitionTime = 0
            if (-this.disY <= this.slideFilishDistance) {
              item_0.scale = -(0.2 / this.slideFilishDistance) * this.disY + 0.8
            }
          }
          // 当前第一个变化
          let item_1 = this.cardArrs[this.currentIndex]
          if (-this.disY <= this.slideFilishDistance) {
            item_1.translateY = -(9 / this.slideFilishDistance) * this.disY + 'vh'
            item_1.transitionTime = 0
            item_1.scale = (0.1 / this.slideFilishDistance) * this.disY + 1
          }
          // 当前第二个变化
          if (this.cardArrs[this.currentIndex + 1]) {
            let item_2 = this.cardArrs[this.currentIndex + 1]
            if (-this.disY <= this.slideFilishDistance) {
              item_2.translateY = -(5 / this.slideFilishDistance) * this.disY + 9 + 'vh'
              item_2.transitionTime = 0
              item_2.scale = (0.05 / this.slideFilishDistance) * this.disY + 0.9
            }
          }
          // 当前第三个变化
          if (this.cardArrs[this.currentIndex + 2]) {
            let item_3 = this.cardArrs[this.currentIndex + 2]
            if (-this.disY <= this.slideFilishDistance) {
              item_3.translateY = -(26 / this.slideFilishDistance) * this.disY + 14 + 'vh'
              item_3.transitionTime = 0
              item_3.scale = (0.35 / this.slideFilishDistance) * this.disY + 0.85
            }
          }
          
        } else if (this.disY > 0) {
        // 向上滑
          this.slideDirection = 1
          // 当前第一个变化
          let item_1 = this.cardArrs[this.currentIndex]
          item_1.translateY = -this.disY + 'px'
          item_1.transitionTime = 0
          item_1.scale = 1
          // 当前第二个变化
          if (this.cardArrs[this.currentIndex + 1]) {
            let item_2 = this.cardArrs[this.currentIndex + 1]
            if (this.disY <= this.slideFilishDistance) {
              item_2.translateY = -(9 / this.slideFilishDistance) * this.disY + 9 + 'vh'
              item_2.transitionTime = 0
              item_2.scale = (0.1 / this.slideFilishDistance) * this.disY + 0.9
            }
          }
          // 当前第三个变化
          if (this.cardArrs[this.currentIndex + 2]) {
            let item_3 = this.cardArrs[this.currentIndex + 2]
            if (this.disY <= this.slideFilishDistance) {
              item_3.translateY = -(5 / this.slideFilishDistance) * this.disY + 14 + 'vh'
              item_3.transitionTime = 0
              item_3.scale = (0.05 / this.slideFilishDistance) * this.disY + 0.85
            }
          }
          // 当前第四个变化
          if (this.cardArrs[this.currentIndex + 3]) {
            let item_4 = this.cardArrs[this.currentIndex + 3]
            if (this.disY <= this.slideFilishDistance) {
              item_4.translateY = -(26 / this.slideFilishDistance) * this.disY + 40 + 'vh'
              item_4.transitionTime = 0
              item_4.scale = (0.35 / this.slideFilishDistance) * this.disY + 0.5
            }
          }
        }
      }
    },
    // 滑动结束
    playerTouchEnd (ev) {
      ev = ev || event
      if (ev.changedTouches.length === 1) {
        this.endY = ev.changedTouches[0].clientY
        console.log('滑动结束-endY', this.endY)
        this.disY = this.startY - this.endY
        if (Math.abs(this.disY) < this.slideDistance) {
        // 滑动距离小于滑动限制的距离,强行回到起点
          this.returnBack()
        } else {
        // 滑动距离大于滑动限制的距离,滑动到最大值
          if (this.slideDirection === 1) {
            this.slideUp()
          } else {
            this.slideDown()
          }
        }
      }
    },
  • 卡牌的动作
    // 回到起点
    returnBack () {
      // 当前第一个变化
      let item_1 = this.cardArrs[this.currentIndex]
      item_1.translateY = 0
      item_1.transitionTime = 1
      item_1.scale = 1
      // 当前第二个变化
      if (this.cardArrs[this.currentIndex + 1]) {
        let item_2 = this.cardArrs[this.currentIndex + 1]
        item_2.translateY = '9vh'
        item_2.transitionTime = 1
        item_2.scale = 0.9
      }
      // 当前第三个变化
      if (this.cardArrs[this.currentIndex + 2]) {
        let item_3 = this.cardArrs[this.currentIndex + 2]
        item_3.translateY = '14vh'
        item_3.transitionTime = 1
        item_3.scale = 0.85
      }
      // 当前第四个变化
      if (this.cardArrs[this.currentIndex + 3]) {
        let item_4 = this.cardArrs[this.currentIndex + 3]
        item_4.translateY = '40vh'
        item_4.transitionTime = 1
        item_4.scale = 0.5
      }
    },
    // 向上滑动切换
    slideUp () {
      if (this.currentIndex === this.cardArrs.length - 1) {
        return this.returnBack()
      }
      // 当前第一个变化
      let item_1 = this.cardArrs[this.currentIndex]
      item_1.translateY = '-160vh'
      item_1.transitionTime = 1
      item_1.scale = 0.5
      // 当前第二个变化
      if (this.cardArrs[this.currentIndex + 1]) {
        let item_2 = this.cardArrs[this.currentIndex + 1]
        item_2.translateY = 0
        item_2.transitionTime = 1
        item_2.scale = 1
      }
      // 当前第三个变化
      if (this.cardArrs[this.currentIndex + 2]) {
        let item_3 = this.cardArrs[this.currentIndex + 2]
        item_3.translateY = '9vh'
        item_3.transitionTime = 1
        item_3.scale = 0.9
      }
      // 当前第四个变化
      if (this.cardArrs[this.currentIndex + 3]) {
        let item_4 = this.cardArrs[this.currentIndex + 3]
        item_4.translateY = '14vh'
        item_4.transitionTime = 1
        item_4.scale = 0.85
      }
      this.currentIndex++
      if (this.currentIndex > this.cardArrs.length - 1) {
        this.currentIndex = this.cardArrs.length - 1
      }
      console.log('currentIndex---', this.currentIndex)
    },
    // 向下滑动切换
    slideDown () {
      if (this.currentIndex === 0) {
        return this.returnBack()
      }
      // 当前上一个变化
      if (this.cardArrs[this.currentIndex - 1]) {
        let item_0 = this.cardArrs[this.currentIndex - 1]
        item_0.translateY = 0
        item_0.transitionTime = 0.6
        item_0.scale = 1
      }
      // 当前第一个变化
      let item_1 = this.cardArrs[this.currentIndex]
      item_1.translateY = '9vh'
      item_1.transitionTime = 0.6
      item_1.scale = 0.9
      // 当前第二个变化
      if (this.cardArrs[this.currentIndex + 1]) {
        let item_2 = this.cardArrs[this.currentIndex + 1]
        item_2.translateY = '14vh'
        item_2.transitionTime = 0.6
        item_2.scale = 0.85
      }
      // 当前第三个变化
      if (this.cardArrs[this.currentIndex + 2]) {
        let item_3 = this.cardArrs[this.currentIndex + 2]
        item_3.translateY = '40vh'
        item_3.transitionTime = 0.6
        item_3.scale = 0.5
      }
      this.currentIndex--
      if (this.currentIndex < 0) {
        this.currentIndex = 0
      }
      console.log('currentIndex---', this.currentIndex)
    }
  • 需要注意的是,在这个 demo 中,卡牌的数量以及 transform 的属性被我写死在了 data 中,而实际项目中往往是动态获取一个不含这些属性的数组,所以需要在获取到数据后动态去添加上 transfrom 的属性:
this.cardArrs.forEach((item, index, arr) => {
    item.zIndex = arr.length - index
    if (index < this.currentIndex) {
        item.scale = 0.5
        item.translateY = '-160vh'
        item.transitionTime = 1
    } else if (index === this.currentIndex) {
        item.scale = 1
        item.translateY = 0
        item.transitionTime = 1
    } else if (index === this.currentIndex + 1) {
        item.scale = 0.9
        item.translateY = '9vh'
        item.transitionTime = 1
    } else if (index === this.currentIndex + 2) {
        item.scale = 0.85
        item.translateY = '14vh'
        item.transitionTime = 1
    } else {
        item.scale = 0.5
        item.translateY = '40vh'
        item.transitionTime = 1
    }
    })

毕竟划起来还是很带感的^_^,感谢阅读!
GitHub 源码地址:https://github.com/Kevin-269581661/vue-slide-card.git

如果觉得还不错的话,给个星吧,谢谢~~~
可以改造为探探等卡片切换效果效果图如下效果简述上下左右均可移动随点击移动在上下左右一定的范围内移动后会返回原来的位置,不会切换只有大于一定范围才会产生切换,同时切换效果随移动方向不同,产生不同的切换效果代码解析wxmlwxml里有两个卡片,相互重叠卡片绑定touch的开始,移动和结束方法,获取点击的位置绑定动画,方便之后的动画操作js在touch的开始方法里,绑定点击的初始位置设置卡片绝对定位,在touch移动的方法里,绑定top和left值,实现随点击点移动而移动在点击的结束方法里获取最后的点击位置,和初始位置做对比,如果x,y移动的大小在一定的范围内,则恢复绝对定位的初始值如果大于x,y设定的范围则实现切换效果特效微信的js动画特效是根据css3封装而来,我当时尝试用css3实现,但结合一些动画出发要求等原因,使用微信的js东特特效还是相对方便绑定了z-index,基本实现两张卡片的相互切换模仿微信小程序文档了动画的效果,注意的就是translate移动之后,一定要再移动回0,这样才能在之后回到卡片初始位置卡片绑定了透明度的变量,根据移动所占百分比,来显示底层的透明度movebox原本分1,2。目前已经合并为一个函数,其他1,2的控制动效控制函数可以适当合并后续这个源码,可以在卡片内插入任意内容,相互切换我自己使用的情况是去除了移动,直接切换,1、2卡片绑定的是同一个变量,由于不能移动,所以看不到下面的内容,一旦成功切换卡片,同时绑定的变量也会切换,达到了内容随卡片切换效果如果不去除移动的话,就不能1、2卡片绑定同一个变量,因为移动上面卡片会看到下面的卡片解决方法1(个人思考,如果有更好的欢迎交流)绑定两个变量,切换卡片的同时,更新该卡片绑定的数据解决方法2(个人思考,如果有更好的欢迎交流)1、2卡片绑定一个数组变量,同时1、2绑定的固定的数组0位和1位数据一旦切换,则分别使用pop或shift,push或unshift进行数组变量的数据更新这样保证了数组数据切换时,卡片数据的相对固定
好的,下面是一个实现层叠卡片滑动切换卡牌动态滑动切换效果Vue.js 实现示例: HTML 代码: ```html <div id="app"> <div class="swiper-container"> <div class="swiper-wrapper"> <div class="swiper-slide" v-for="(item, index) in cardList" :key="index"> <div class="card"> <div class="card-header">{{ item.title }}</div> <div class="card-body">{{ item.content }}</div> </div> </div> </div> <div class="swiper-pagination"></div> </div> </div> ``` CSS 代码: ```css .swiper-container { width: 100%; height: 300px; position: relative; } .swiper-wrapper { position: relative; width: 100%; height: 100%; } .swiper-slide { position: absolute; left: 0; top: 0; width: 100%; height: 100%; transition: transform 0.5s ease-in-out; } .swiper-pagination { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); } .card { background-color: #fff; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 0 5px #ccc; padding: 10px; width: 80%; margin: 0 auto; } .card-header { font-weight: bold; font-size: 18px; } .card-body { margin-top: 10px; } ``` JavaScript 代码: ```javascript new Vue({ el: '#app', data: { currentIndex: 0, cardList: [ { title: 'Card Title 1', content: 'Card Content 1' }, { title: 'Card Title 2', content: 'Card Content 2' }, { title: 'Card Title 3', content: 'Card Content 3' }, { title: 'Card Title 4', content: 'Card Content 4' }, { title: 'Card Title 5', content: 'Card Content 5' } ] }, mounted() { this.initSwiper(); }, methods: { initSwiper() { const swiper = new Swiper('.swiper-container', { loop: true, centeredSlides: true, slidesPerView: 'auto', pagination: { el: '.swiper-pagination', clickable: true }, on: { slideChangeTransitionEnd: () => { this.currentIndex = swiper.realIndex; } } }); } } }); ``` 在这个示例中,我们使用了 Vue.js 和 Swiper 插件实现了层叠卡片滑动切换卡牌动态滑动切换效果。具体来说,我们首先在 HTML 中创建了一个 `.swiper-container` 容器,然后在其中创建了若干个 `.swiper-slide` 卡片容器,每个卡片容器中包含了一个 `.card` 容器作为卡片的内容。我们使用了 Vue.js 的 `v-for` 指令动态渲染了卡片容器,并使用了 Swiper 的 `slidesPerView: 'auto'` 配置来实现卡片数量自适应。 在 JavaScript 中,我们使用了 Vue.js 的数据绑定机制来动态更新卡片容器的 `currentIndex` 属性,并在 Swiper 的 `slideChangeTransitionEnd` 事件中更新了 `currentIndex` 属性。最后,我们在 `mounted` 钩子函数中调用了 `initSwiper()` 方法来初始化 Swiper 插件。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值