浅谈在项目中手写轮播

799 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

前言

最近,公司官网改版,整的花里胡哨的。既然花,那就免不了有很多轮播设计,很多的交互也比较复杂。今天就来浅谈一下最近写的轮播吧。

提起轮播,大家可能第一反应就是swiper.js,哈,我也不例外。因为公司项目使用nuxt,那就很自然的,轮播我首选vue-awesome-swiper了。由于项目比较早,所以swiper版本也比较低。低版本swiper很多花哨的交互也满足不了。就得我们自己手写实现了。

实现

调用swiper库

首先,先看看基础设计吧。

image.png

看到这个设计,是不是想到了swiper中的这个demo

刚开始我也是这样想的,等到实际应用时,问题频出。我们可以确定的事,目前两个轮播的option是这样的。

<div ref="swiper" v-swiper:mySwiper="swiperOption" class="mySwiper">
    div class="swiper-wrapper">
        <div
            v-for="(item, index) in list"
            :key="index"
            class="swiper-slide"
        >
            <img :src="item.img" alt="img" class="item-img">
        </div>
    </div>
</div>

<div
    ref="bottomSwiper"
    v-swiper:bottomSwiper="bottomSwiperOption"
    class="bottomSwiper"
>
    <div class="swiper-wrapper">
        <div
            v-for="(item, index) in list"
            :key="index"
            class="swiper-slide"
        >
            <img :src="item.img" alt="img" class="item-img">
        </div>
    </div>
</div>
computed: {
    // 大图轮播
    swiperOption() {
        return {
        };
    },
    // 底部轮播
    bottomSwiperOption() {
        return {
        };
    },
},

而官方例子中,两个轮播这样关联:

var swiper = new Swiper(".mySwiper", {
    loop: true,
    spaceBetween: 10,
    slidesPerView: 4,
    freeMode: true,
    watchSlidesProgress: true,
});
var swiper2 = new Swiper(".mySwiper2", {
    loop: true,
    spaceBetween: 10,
    navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev",
    },
    thumbs: { // thumbs组件专门用于制作带缩略图的swiper
        swiper: swiper,
    },
});

那按照官方我们是不是应该这样实现?

computed: {
    // 大图轮播
    swiperOption() {
        const self = this;
        return {
            thumbs: {
                swiper: self.bottomSwiperOption,
            },
        };
    },
    // 底部轮播
    bottomSwiperOption() {
        return {
        };
    },
},

很不幸的事,这样会报错。同样的方式,使用controller 来绑定两个轮播同样会报错。那可能大家会说,这样肯定不行,得用ref来获取绑定。啊,我也尝试了,可惜在内部得到的结果是这样this.$refs.swiper.swiper === undefined。啊这,难道没办法了吗?不能用轮子了吗?哎,当然不是,有请slideToslideChangeTransitionStart上场。此时,我们的options就变成了这样。

computed: {
    // 大图轮播
    swiperOption() {
    },
    // 底部轮播
    bottomSwiperOption() {
        const self = this;
        return {
            // 每一次轮播展示的图片数
            slidesPerView: 6,
            // 点击卡片切换
            slideToClickedSlide: true,
            // 循环
            loop: self.list.length > 1,
            // 自动播放
            autoplay: {delay: 2000, disableOnInteraction: false},
            // 卡片之间间距
            spaceBetween: 20,
            // 鼠标形状改变
            grabCursor: true,
            on: {
                // 切换开始前回调
                slideChangeTransitionStart() {
                    const index = this.activeIndex - 6;
                    if (index === that.list.length) {
                        that.$refs.swiper.swiper.slideTo(0);
                    }
                    else if (index === -1) {
                        that.$refs.swiper.swiper.slideTo(that.list.length - 1);
                    }
                    else {
                        that.$refs.swiper.swiper.slideTo(index);
                    }
                },
            },
        };
    },
},

这样就简单的实现了底部轮播控制上边轮播展示。

手写

如果仅仅是这样,那也不值得写一篇文章了,有趣的来了,由于底部轮播效果是往前顶,类似于走马灯的效果,UI不满意,UI想要一个一个往后走,走到最大显示个数,保持最后一个选中不动,往前顶一位,直到最后一位下来又回滚到第一位。emmm,好像也不难对吧,写个简单的demo实现一下吧。

先看看效果图。

先来确定页面结构。

<div class="box">
    <div class="boxs guodu" ref="swiper">
        <div class="itemBox" @click="boxClick">
            <div v-for="(item, i) in list" class="items" :key="i">
                <div :class="index === i ? 'name active' : 'name'" :index="i">
                    {{item.name}}
                </div>
            </div>
        </div>
    </div>

    <button @click="clickright">+1</button>
    <button @click="clickleft">-1</button>
</div>

再来写点简单的样式以及过渡效果。

.box {
    width: 100%;
    height: 100%;
}

.boxs {
    width: 1100px;
    position: relative;
}

.itemBox {
    height: 160px;
    overflow: hidden;
}

.items {
    margin-right: 20px;
    display: inline-block;
}

.name {
    width: 160px;
    height: 160px;
    background-color: #abc;
    text-align: center;
    line-height: 160px;
    font-size: 50px;
}

.active {
    width: 180px;
    background-color: pink;
}

.guodu {
    transition: left .2s, width .2s;
}

然后到了重头戏。js实现简单的交互。先来实现下一张的逻辑

// 通过index来确定当前哪一个是被选中的
clickright() {
    // 每次点击index都会加1
    this.index++
    const box = this.$refs.swiper
    // 最大显示6个,当大于6个小于list长度时,控制左移一个格子
    if(this.index > 5 && this.index < this.list.length) {
        box.style.left = (-180 * (this.index - 5)) + 'px'
        box.style.width = box.clientWidth + 180 + 'px'

    }
    // 超过length,回到第一个
    if(this.index >= this.list.length) {
        this.index = 0
        box.style.left = 0 + 'px'
        box.style.width = 1100 + 'px'
    }
},

然后就是上一张的逻辑

clickleft() {
    // 点击上一张按钮,index减1
    this.index--
    const box = this.$refs.swiper
    // 如果小于0,需要回到尾部
    if(this.index < 0) {
        this.index = this.list.length - 1
        if(this.list.length > 6){
            box.style.left = -180 * (this.list.length - 6) + 'px'
            box.style.width = 1100 + 180 * ((this.list.length - 6)) + 'px'
        }
    }else if(this.index > 4) {
        // 可以思考一下为什么与4比较,尾部往前移,右移一格距离
        box.style.left = (-180 * (this.index - 5)) + 'px'
        box.style.width = box.clientWidth - 180 + 'px'
    }
},

还有就是点击任意格切换的逻辑

boxClick(e) {
    const index = e.target.attributes?.index?.value
    const box = this.$refs.swiper
    // 确定index然后切换就行
    if(index){
        if(index < 6) {
            box.style.left = 0 + 'px'
            box.style.width = 1100 + 'px'
        }
        this.index = +index
    }
}

再来就是让他自动切换的逻辑了

mounted() {
    this.swiperLoop()
},
clickright() {
    ... 逻辑代码

    this.swiperLoop()
}
clickleft() {
    ... 逻辑代码

    this.swiperLoop()
}

boxClick(e) {
    const index = e.target.attributes?.index?.value
    if(index){
        this.index = +index
        this.swiperLoop()
    }
}

swiperLoop() {
    clearInterval(this.interval)
    this.interval = setTimeout(this.clickright, 2000)
}

这样我们底部就按照预期完成了。至于与顶部关联就留给各位自己实现吧。

总结

本文记载了笔者最近在项目中碰到的轮播,在实现过程中碰到的一些问题与解决方法(仅个人观点),有更好的方法欢迎评论区留言。