使用vue-awesome-swiper实现缩略图

2,920 阅读1分钟

这里使用的是vue-awesome-swiper3.1.3

这是官网的例子

<template>
  <div class="imageGroup clearfix">
    <swiper :options="swiperOptionTop" class="gallery-top" ref="swiperTop">
      <swiper-slide v-for="(item, index) in imgList" :key="index">
        <img :src="item" class="img" />
      </swiper-slide>
    </swiper>
    <!-- swiper2 Thumbs -->
    <div class="thumbsBox">
      <i @click="change('prev')" class="el-icon-arrow-left prevBtn" :class="{ btnDisabled: swiperobj1.activeIndex === 0 }"></i>
      <swiper :options="swiperOptionThumbs" class="gallery-thumbs" ref="swiperThumbs">
        <swiper-slide v-for="(item, index) in imgList" :key="index">
          <img :src="item" class="img" />
        </swiper-slide>
      </swiper>
      <i @click="change('next')" class="el-icon-arrow-right nextBtn" :class="{ btnDisabled: swiperobj1.activeIndex === imgList.length - 1 }"></i>
    </div>
  </div>
</template>
<script>
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
export default {
  name: 'imageGroup',
  components: {
    swiper,
    swiperSlide
  },
  props: {
    height: {
      type: String,
      default: '100px'
    },
    imgList: {
      type: Array,
      default: () => []
    }
  },
  data () {
    return {
      swiperOptionTop: {
        spaceBetween: 10
      },
      swiperOptionThumbs: {
        spaceBetween: 10,
        centeredSlides: true,
        slidesPerView: 'auto',
        touchRatio: 0.2,
        slideToClickedSlide: true
      },
      isMounted: false
    }
  },
  computed: {
    swiperobj1 () {
      if (!this.isMounted) return { index: 0 }
      return this.$refs.swiperTop.swiper
    },
    swiperobj2 () {
      if (!this.isMounted) return
      return this.$refs.swiperThumbs.swiper
    }
  },
  mounted () {
    this.$nextTick(() => {
      const swiperTop = this.$refs.swiperTop.swiper
      const swiperThumbs = this.$refs.swiperThumbs.swiper
      swiperTop.controller.control = swiperThumbs
      swiperThumbs.controller.control = swiperTop
      this.isMounted = true
    })
  },
  methods: {
    change (type) {
      let activeIndex = this.swiperobj1.activeIndex
      if (type === 'prev' && activeIndex !== 0) {
        this.swiperobj1.slideTo(activeIndex - 1, 100)
        this.swiperobj2.slideTo(activeIndex - 1, 100)
      }
      if (type === 'next' && activeIndex !== this.imgList.length) {
        this.swiperobj1.slideTo(activeIndex + 1, 100)
        this.swiperobj2.slideTo(activeIndex + 1, 100)
      }
    }
  }
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.imageGroup, .swiperBox, .imgBox {
  height: 100%;
}
.swiper-slide {
  background-size: cover;
  background-position: center;
}
.gallery-top {
  height: 80%!important;
  width: 100%;
}
.thumbsBox {
  padding: 0 20px;
  height: 20%!important;
  position: relative;
}
.gallery-thumbs {
  height: 100%!important;
  box-sizing: border-box;
  padding: 1px 0;
}
.gallery-thumbs .swiper-slide {
  width: 25%;
  height: 100%;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
.gallery-thumbs .swiper-slide-active {
  opacity: 1;
  border: 1px solid #2D9AE9;
}
.prevBtn, .nextBtn {
  color: #666;
  position: absolute;
  font-size: 30px;
  font-weight: bold;
  top: calc(50% - 15px);
}
.prevBtn {
  left: -3px;
}
.nextBtn {
  right: -3px;
}
.btnDisabled {
  color: #ccc;
  cursor: not-allowed;
}
.img {
  width: 100%;
  height: 100%;
}
</style>

效果图如下:

image.png 当前的缩略图在中间显示,这样看起来比较奇怪

这是修改后的代码

<template>
  <div class="imageGroup clearfix">
    <swiper :options="swiperOptionTop" class="gallery-top" ref="swiperTop">
      <swiper-slide v-for="(item, index) in imgList" :key="index">
        <img :src="item" class="img" />
      </swiper-slide>
    </swiper>
    <!-- swiper2 Thumbs -->
    <div class="thumbsBox">
      <i @click="change('prev')" class="el-icon-arrow-left prevBtn" :class="{ btnDisabled: swiperobj1.activeIndex === 0 }"></i>
      <swiper :options="swiperOptionThumbs" class="gallery-thumbs" ref="swiperThumbs">
        <swiper-slide v-for="(item, index) in imgList" :key="index" :class="{ active: swiperobj1.activeIndex === index}">
          <img @click="setIndex(index)" :src="item" class="img" />
        </swiper-slide>
      </swiper>
      <i @click="change('next')" class="el-icon-arrow-right nextBtn" :class="{ btnDisabled: swiperobj1.activeIndex === imgList.length - 1 }"></i>
    </div>
  </div>
</template>
<script>
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
export default {
  name: 'imageGroup',
  components: {
    swiper,
    swiperSlide
  },
  props: {
    height: {
      type: String,
      default: '100px'
    },
    imgList: {
      type: Array,
      default: () => []
    }
  },
  data () {
    return {
      swiperOptionTop: {
        spaceBetween: 10
      },
      swiperOptionThumbs: {
        spaceBetween: 10,
        slidesPerView: 4,
        freeMode: true,
        watchSlidesVisibility: true,
        watchSlidesProgress: true
      },
      isMounted: false
    }
  },
  computed: {
    swiperobj1 () {
      if (!this.isMounted) return { index: 0 }
      return this.$refs.swiperTop.swiper
    },
    swiperobj2 () {
      if (!this.isMounted) return
      return this.$refs.swiperThumbs.swiper
    }
  },
  mounted () {
    this.$nextTick(() => {
      const swiperTop = this.$refs.swiperTop.swiper
      const swiperThumbs = this.$refs.swiperThumbs.swiper
      // swiperTop.controller.control = swiperThumbs
      // swiperThumbs.controller.control = swiperTop
      swiperTop.thumbs.swiper = swiperThumbs
      this.isMounted = true
    })
  },
  methods: {
    change (type) {
      let activeIndex = this.swiperobj1.activeIndex
      if (type === 'prev' && activeIndex !== 0) {
        this.swiperobj1.slideTo(activeIndex - 1, 100)
        this.swiperobj2.slideTo(activeIndex - 1, 100)
      }
      if (type === 'next' && activeIndex !== this.imgList.length) {
        this.swiperobj1.slideTo(activeIndex + 1, 100)
        this.swiperobj2.slideTo(activeIndex + 1, 100)
      }
    },
    setIndex (index) {
      this.swiperobj1.slideTo(index, 100)
      this.swiperobj2.slideTo(index, 100)
    }
  }
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.imageGroup, .swiperBox, .imgBox {
  height: 100%;
}
.swiper-slide {
  background-size: cover;
  background-position: center;
}
.gallery-top {
  height: 80%!important;
  width: 100%;
}
.thumbsBox {
  padding: 0 20px;
  height: 20%!important;
  position: relative;
}
.gallery-thumbs {
  height: 100%!important;
  box-sizing: border-box;
  padding: 1px 0;
}
.gallery-thumbs .swiper-slide {
  height: 100%;
  border: 1px solid #ccc;
  box-sizing: border-box;
}
// .gallery-thumbs .swiper-slide-active {
//   border: 1px solid #2D9AE9;
// }
.gallery-thumbs .active {
  border: 1px solid red;
}
.prevBtn, .nextBtn {
  color: #666;
  position: absolute;
  font-size: 30px;
  font-weight: bold;
  top: calc(50% - 15px);
}
.prevBtn {
  left: -3px;
}
.nextBtn {
  right: -3px;
}
.btnDisabled {
  color: #ccc;
  cursor: not-allowed;
}
.img {
  width: 100%;
  height: 100%;
}
</style>

image.png

注意:第二个与第一个不一样的地方是,动态取当前轮播图的index然后和缩略图的index进行对比,相等就加上active这个类名

这里面还有一个知识点, 解决Vue computed计算属性中使用$refs进行dom操作时出现undefined问题

问题分析: computed中 return一个常量时不会再触发数据双向绑定,一开始$refs无效,此时computed属性返回了一个常量值,所以后续不在触发

解决办法 定义一个isMounted变量,并将其引入到计算属性property中。

export default {
    data(){
        return {
            isMounted: false
        }
    },
    computed:{
        property(){
            if(!this.isMounted)
                return;
            //$refs is available
        }
    },
    mounted(){
        this.isMounted = true;
    }
}

另外我们需要注意 computed中 return一个常量时不会再触发数据双向绑定