魔改小程序swiper组件实现某拼夕夕轮播效果

2,879 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

开头

某拼夕夕轮播效果APP上原效果大致如上图(将就着看吧),整体来说也挺简单的一个小效果的。

我们先分析一波:

  • 同时展示多个头像进行轮播,这个微信小程序的swiper组件的display-multiple-items属性就能轻松解决。
  • 左边出去一个淡出效果,但是swiper组件轮播出去直接就是溢出隐藏overflow: hidden,所以这里还要想个办法。
  • 右边进入一个放大效果及外层一个简单的圆环效果。

大致实现这三步就行啦?那肯定不行了,你太小看产品经理了,加,我们抄也要抄得不一样,有个性:

  • 先轮播反个方向,从左向右,可惜Swiper组件没有定义方向的属性,所以我们只能去手动反转180度了。
  • 不固定死6个用户信息,当四个用户信息就开始轮播三个,五个就轮播四个,最多就展示六个。(听不懂??? 继续看下面咯)

正戏开始

一、先创建项目,写好基本的结构、样式。

index.wxml 文件

// index.wxml 完整代码
<view class="container">
    <!-- 轮播框 -->
    <view wx:if="{{photosData.length>=4}}" style="width: {{swiperBoxWidth + 'rpx'}}" class="photos__swiper-box">
        <swiper bindanimationfinish="bindanimationfinish" bindchange="bindchange" circular="{{true}}"
                duration="{{1000}}" interval="{{interval}}" autoplay="{{true}}" display-multiple-items="{{showNumberItem}}"
                class="photos__swiper">
            <block wx:for="{{photosData}}" wx:key="index">
                <swiper-item catchtouchmove="stopTouchMove">
                    <view class="photos__item">
                        <view  class="photos__avatar">
                            <image class="photos__avatar-img" src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/1/6/16f786fbd44167e9~tplv-t2oaga2asx-image.image"></image>
                            <view style="position: absolute;color: red;z-index: 10;bottom: 0;transform:rotate(180deg);font-size: 36rpx;font-weight: bold;">{{index + 1}}</view>
                            <view class="photo__circle"></view>
                        </view>
                    </view>
                </swiper-item>
            </block>
        </swiper>
        <!-- 更多按钮, 遮住第一个 -->
        <view class="photos__more">
            <image class="photos__more-img" src="../../assets/images/more.png"></image>
            <view class="photos__more-block"></view>
        </view>
    </view>
</view>

index.wxss 文件

.container{
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 100rpx 0;
}
.photos__swiper-box{
  transform: rotate(180deg);
  position: relative;
  width: 360rpx;
  height: 84rpx;
}
.photos__swiper{
  width: 100%;
  height: 100%;
  position: relative;
}
.photos__item{
  position: relative;
  width: 90rpx;
  height: 80rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}
.photos__avatar{
  width: 68rpx;
  height: 68rpx;
  position: relative;
  opacity: 1;
}
.photos__avatar--scale{
  transform: scale(0);
}
.photos__avatar-img{
  width: 100%;
  height: 100%;
  border-radius: 50%;
  transform: rotate(180deg);
}

/*更多按钮*/
.photos__more{
  width: 90rpx;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  transform: rotate(180deg);
  display: flex;
  justify-content: center;
  align-items: center;
}
.photos__more-img{
  width: 68rpx;
  height: 68rpx;
  position: relative;
  z-index: 1;
  border-radius: 50%;
}
.photos__more-block{
  position: absolute;
  z-index: 0;
  right: 0;
  width: 40rpx;
  height: 100%;
  background-color: #fff;
}

/*红色圆环*/
.photo__circle{
  position: absolute;
  width: 80rpx;
  height: 80rpx;
  border-radius: 50%;
  border: 2rpx dashed #FF6464;
  top: -6rpx;
  left: -6rpx;
  box-sizing: border-box;
  opacity: 0;
  transform: rotate(0deg);
}

index.js

const app = getApp()
Page({
  data: {
    swiperBoxWidth: 360, // 一个swiper-item是90rpx. 假设展示3个,则4*90; 展示4个,则5*90 ...
    showNumberItem: 4, // 展示的个数, 4/5/6/6+四种情况, 为了效果默认会加上一个; 注意这个数不能大于数组长度
    interval: 5000, // 多久滑动一次
    swiperIndex: 0, // 当前swiper所在的索引
    photosData: [{}, {}, {}, {}, {}],
  }
})
  • 大致上面的代码,能实现一个同时展示多个头像的自动轮播,并且我通过rotate(180deg)给整个盒子旋转了180度,让轮播方向从左到右。
  • 我们需要注意的是swiper组件的display-multiple-items属性,当你同时展示了4个元素,那么你的<swiper-item/>必须要有五个才行,这样才能自动轮播。
  • 为了实现上面 分析第二步骤 的淡出轮播效果,我想了个办法,就是我们让每次轮播出去的那个元素的下一个元素加个淡出效果,再把固定在右边的“更多按钮”元素定位到swiper组件上面,这样子就能实现了。(看不懂的自己想,哈哈~)

二、出去与进来的动画效果

动画效果这里主要是用到了CSS3的动画(animation)来实现,主要的想法就是在对应的时机添加相应的类名来完成,就是怎么简单 easy~ 。

/* 动画 */
.fade-out{ /*淡出*/
  transition: 1000ms linear;
  transition-property: transform, opacity;
  transform-origin: 50% 50% 0;
  opacity: 0;
}
.scale-in{ /*放大进入*/
  transition: 500ms ease;
  transition-property: transform;
  transform-origin: 50% 50% 0;
  transform: scale(1);
}
.rotate-in{ /*旋转消失*/
  animation: rotateOpacity 1800ms linear 700ms; /*放大进入的动画大概300ms,但中间停留个200ms做准备*/
}
.opacity-in{
  animation: opacity 6000ms ease;
}
@keyframes rotateOpacity{
  0% {
    opacity: 0;
    transform: rotate(0deg);
  }
  10% {
      opacity: 1;
  }
  80% {
      opacity: 0.8;
  }
  100% {
    opacity: 0;
    transform: rotate(120deg);
  }
}
@keyframes opacity{
  0%{
      opacity: 0.2;
  }
  50%{
      opacity: 1;
  }
  100%{
      opacity: 0;
  }
}

动画效果这里没啥好说的,因为是高仿也拿不到拼多多的动画效果具体数值,所以基本都是靠感觉啦。

补:
animation: name duration timing-function delay iteration-count direction;

我们找到对应的元素添加相关的动画类名

<view class="{{item.outAni ? 'fade-out' : ''}} {{item.inAni ? 'scale-in' : ''}} {{item.scale ? 'photos__avatar--scale' : ''}} photos__avatar">
  ...
  <view class="{{item.inAni ? 'rotate-in' : ''}} photo__circle"></view>
</view>

因为很多时候信息都是通过接口请求回来的,所以我们先简单模拟一下请求数据。

onLoad: function() {
  setTimeout(() => {
      let result = [{}, {}, {}, {}];
      // 因为有一个被遮挡, 所以要复制一个元素
      if(result.length > 3 && result.length <= 7) {
        result.push(JSON.parse(JSON.stringify(result[0])))
      }
      this.setData({
        photosData: result,
        showNumberItem: this.getShowItemNumber(result.length),
        swiperBoxWidth: this.pxToRpx(this.rpxToPx(90) * this.getShowItemNumber(result.length)), // swiper需求要一个宽度
      })
  }, 800)
},
// 获取swiper应该同时展示多少个
getShowItemNumber(num) {
  switch(num) {
    case 5: return 4; break;
    case 6: return 5; break;
    case 7: return 6; break;
    default: return 7;
  }
},
// rpx转px
rpxToPx(rpx) {
  return rpx / 750 * wx.getSystemInfoSync().windowWidth;
},      
// px转rpx
pxToRpx(px) {
  return  px * 750 / wx.getSystemInfoSync().windowWidth;
},

以上JS逻辑只是做了一件事情就是计算一个这个Swiper组件的宽度,因为我们上面 分析第五步骤 说过,我们展示的个数是不确定的,所以我们的个数由返回的数据来决定。

然后我们要做的就是何时让这些类名生效,何时添加这些类名? 这个时机是个重点,主要我们会用到Swiper组件的bindchange()bindanimationfinish()事件,下面我们实现这个事件的逻辑就大功告成了。

const app = getApp()
Page({
  ...,
  bindchange(e) {
    let current = e.detail.current;
    let swiperIndex = this.data.swiperIndex;
    let swiperData = this.data.photosData;

    // 1.整体初始化
    swiperData.forEach(item => {
      item.outAni = false;
      item.inAni = false;
      item.scale = false; // 进入时要放大, 所以要先将其缩放为0
    });

    // 2.给需要动画的元素打标识
    if(swiperIndex >= swiperData.length - 1) {
      swiperData[0].outAni = true;
    }else {
      swiperData[swiperIndex+1].outAni = true;
    }
    swiperData[this.getNewIndex(swiperIndex, this.data.showNumberItem, swiperData.length)].scale = true;
    // 3. 最后赋值
    this.setData({
      swiperIndex: current,
      photosData: swiperData,
    })
  },
  bindanimationfinish(e) {
    let swiperIndex = this.data.swiperIndex;
    let swiperData = this.data.photosData;
    let targetIndex = this.getNewIndex(swiperIndex - 1, this.data.showNumberItem, swiperData.length);
    // 放大进入
    swiperData[targetIndex].inAni = true;

    this.setData({
      photosData: swiperData,
    })
  },
  // 由一个索引值添加一定增量后获取另一个索引值
  getNewIndex(I, R, L) {
    /**
     * @param {*} I: 取的索引值
     * @param {*} R: 增量
     * @param {*} L: 添加增量后的索引值
     */
    return (I + R) >= L ? (I + R - L) : (I + R)
  }
})

主要添加类名的时机是重点,自己细细品吧,哈哈~

源码

以上源码请点它。传送门






至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。