基于uniapp带你实现了一个好看的轮播图组件

5,616 阅读4分钟

vi-swiper-66.gif

背景

最近,朋友说在做uniapp微信小程序项目时接到一个需求,需要实现一个类似上图的轮播图效果,问我有没有什么好的实现方案,他也和我说了他的方案,比如让产品直接把图片切成两部分再分别进行上传,但是我觉得这个方案不够灵活,当每次修改banner图片时,都得让产品把图切好再分别上传。下文将探究一个更好的实现思路。

微信图片_20240607010418.jpg

需求分析

由文章顶部的gif动图,我们可以看出每次执行轮播动画时,只会裁剪图片的中间部分进行滚动,图片的其余部分保持不变,等待轮播动画执行完成后,再淡化背景图片切换到下一张轮播图。

从中可得出两点关键信息

1.两种相同图片堆叠在一起,一张背景图(大图),一张轮播图(小图);

2.需要对图片中间部分进行裁剪,并且定位到刚好能够和背景图重合得区域;

根据以上得出的信息,我们还需解决两个疑问:

1.如何对图片进行裁剪?

2.图片裁剪后如何定位和背景图重合的区域?

前端裁剪图片可以使用canvans,但是兼容性不好,太麻烦!还有没有好一点的方法呢?当然有,参考css中的雪碧图进行图片裁剪显示!!但还是有些麻烦,还有没有简单的方式呢?有,那就是使用css属性overflow: hidden;进行图片裁剪,下文也主要是讲这个方案。

开始实现

vi-swiper.vue

<template>
 <view class="v-banner" :style="[boxStyle]">
   <swiper class="v-swiper" autoplay indicator-dots circular 
     @animationfinish="onAnimationfinish"
   >
     <swiper-item class="v-swiperi-tem" v-for="(url,index) in list" :key="index">
       <image class="v-img" :src="url" mode="scaleToFill"></image>
     </swiper-item>
   </swiper>
 </view>
</template>

<script>
 export default {
   props: {
     // 当前索引
     value: {
       type: Number,
       default: 0
     },
     // 轮播图列表
     list: {
       type: Array,
       default: () => []
     }
   },
   computed: {
     boxStyle() {
       return {
         backgroundImage: `url(${this.list[this.value]})`,
         // 开启background-image转场动画
         transition: '1s background-image'
       }
     }
   },
   methods: {
     // 轮播图动画结束后更新底部更新图索引
     onAnimationfinish(e) {
       this.$emit('input', e.detail.current)
     }
   }
 }
</script>

<style lang="scss">
/*sass变量,用于动态计算*/
$swiperWidth: 650rpx;
$swiperHeight: 350rpx;
$verticalPadding: 60rpx;
$horizontalPadding: 50rpx;
$imgWidth: $swiperWidth + $horizontalPadding * 2;
$imgHeight: $swiperHeight + $horizontalPadding * 2;

.v-banner {
 /* 因为需要根据内边距动态调节背景图宽高,所以设为行内块 */
 display: inline-block;
 // 背景图铺满容器
 background-size: 100% 100%;
 padding: $verticalPadding $horizontalPadding;
 .v-swiper {
   height: $swiperHeight;
   width: $swiperWidth;
   // 裁剪图片
   overflow: hidden;
   .v-swiperi-tem {
     .v-img {
       width: $imgWidth;
       height: $imgHeight;
     }
   }
 }
}
</style>

以上代码主要实现思路是让底部背景图大小和轮播图大小相同使两种重合,尺寸相等才能重合swiper轮播图容器组件固定宽高,使用overflow: hidden;来裁剪内部图片, 然后给底部背景图容器使用padding内边距来撑开容器,达到两种图片堆叠的效果;图片转场通过transition设置动画。

以上组件页面显示效果如下:

image.png

发现两张图片还没有重合在一起,原因是两张图片虽然大小一致了,但是位置不对,如下图所示:

image.png

那么由上图描绘的信息可知,想要两张图重合,那么需要把轮播图分别向上和向左移动对应的内边距距离即可,我们可以通过给轮播图设置负的外边距实现,样式如下:

.v-img {
  ...
  // 使两张图片重合
  margin-top: -$verticalPadding;
  margin-left: -$horizontalPadding;
}

效果如下图所示:

image.png

到这我们实现了图片的裁剪和重合,已经实现了最终效果。完整的代码会在文章结尾附上。

另外,我已经把这个组件发布到了uniapp插件市场,并且做了相应封装,可灵活定制轮播图大小及相关样式,感兴趣的可以点击这里:

vi-swiper轮播图,跳转到文档查阅源码或使用。

总结

这个堆叠轮播图效果实现起来不难,主要是要找对思路,一句话概括就是两张大小相等的图片进行重合,轮播图容器对图片进行裁剪

完整代码

vi-swiper.vue

<template>
  <view class="v-banner" :style="[boxStyle]">
    <swiper class="v-swiper" autoplay indicator-dots circular 
      @animationfinish="onAnimationfinish"
    >
      <swiper-item class="v-swiperi-tem" v-for="(url,index) in list" :key="index">
        <image class="v-img" :src="url" mode="scaleToFill"></image>
      </swiper-item>
    </swiper>
  </view>
</template>

<script>
  export default {
    props: {
      // 当前索引
      value: {
        type: Number,
        default: 0
      },
      // 轮播图列表
      list: {
        type: Array,
        default: () => []
      }
    },
    computed: {
      boxStyle() {
        return {
          backgroundImage: `url(${this.list[this.value]})`,
          // 开启background-image转场动画
          transition: '1s background-image'
        }
      }
    },
    methods: {
      // 轮播图动画结束后更新底部更新图索引
      onAnimationfinish(e) {
        this.$emit('input', e.detail.current)
      }
    }
  }
</script>

<style lang="scss">
/*sass变量,用于动态计算*/
$swiperWidth: 650rpx;
$swiperHeight: 350rpx;
$verticalPadding: 60rpx;
$horizontalPadding: 50rpx;
$imgWidth: $swiperWidth + $horizontalPadding * 2;
$imgHeight: $swiperHeight + $horizontalPadding * 2;

.v-banner {
  /* 因为需要根据内边距动态调节背景图宽高,所以设为行内块 */
  display: inline-block;
  // 背景图铺满容器
  background-size: 100% 100%;
  padding: $verticalPadding $horizontalPadding;
  .v-swiper {
    height: $swiperHeight;
    width: $swiperWidth;
    // 裁剪图片
    overflow: hidden;
    .v-swiperi-tem {
      .v-img {
        width: $imgWidth;
        height: $imgHeight;
        margin-top: -$verticalPadding;
        margin-left: -$horizontalPadding;
      }
    }
  }
}
</style>