Vue-awes-swiper 3.1.3 is used here

Here’s an example from the official website

<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>Copy the code

The renderings are as follows:

The current thumbnail is displayed in the middle, which looks strange

This is the modified code

<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>Copy the code

Note: the second difference from the first one is that the index of the current wheel image is dynamically compared to the index of the thumbnail image. If the index is equal, the active class name is added

There is also a tip for solving the undefined problem with dom manipulation using $refs in a computed property in Vue

Problem analysis: Two-way data binding is no longer triggered when a constant is returned in computed. At first, $REFs does not work, and computed property returns a constant value, so it is not triggered later

The solution is to define an isMounted variable and introduce it into the evaluated property.

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

In addition, we need to note that a return of a constant in computed does not trigger two-way data binding