类swiper组件封装实现

73 阅读1分钟

背景

业务开发中需要一个像swiper一样展示的效果,没有要求自动来回轮播,功能比较简单,使用swiper包有比较大,所以准备自己简单实现一个

实现思路

90199988-f0b8-4930-8ae9-0e95d732060c.png 计算展示框宽度,以及可以展示的卡片数量。可以存放时,没有左右移动按钮,存放不下时,默认展示可以存放的卡片,每次移动一个卡片宽度的距离,实现卡片的移动展示,最后一个卡片移动到完全展示即可

注意要点与解决

  1. 不同电脑下页面展示框的宽度不一样,需要实时监听计算 解决方案:使用浏览器提供的ResizeObserver Api,可以实时监听展示容器的宽度是否发生变化(刚开始监听的浏览器窗口变化,获取dom宽度,存在浏览器窗口不发生变化时,组件无法实时获取宽度【即弹框内等其他动态生成的dom内无法使用】)
  2. 一个组件多处使用,确保不受影响 解决方案:使用 this.$ref获取dom元素 不需要父元素组件定义id,更友好的实现

代码实现

子组件实现

<template>
    <section @ref="swiper-wrap" class="swiper-wrap">
        <ul
        class="flex swiper-list"
        :style="{marginLeft: `${(options.width + options.itemSpacing) * offestNum + scroll}px`}">
            <li
                class="swiper-list-item"
                v-for="(item, index) in data"
                :key="item[options.key]"
                :style="{
                    width: `${options.width}px`,
                    marginRight: index === data.length - 1 ? `0px` :`${options.itemSpacing}px` }"
            >   
                <slot :item="item"></slot>
            </li>
        </ul>
        <div class="flex flex-center button-wrap prev" @click="goLeft" v-show="offestNum < 0">
            <img src="@/assets/card/arrowLeft.png" alt="左移" />
        </div>
        <div class="flex flex-center button-wrap next" @click="goRight" v-show="offestNum > showNum - data.length">
            <img src="@/assets/card/arrowRight.png" alt="左移" />
        </div>
    </section>
</template>

<script lang="ts">
/**
 * @description 类swiper组件
 * @fileName swiperList.vue
 * @author maqianbo
 * @date 2021/04/28 17:32:29
 * @version V1.0.0
 */
import { Component, Prop, @Ref, Vue } from 'vue-property-decorator';
import ResizeObserver from 'resize-observer-polyfill';

export interface SwiperListOptionVo {
    width: number;
    itemSpacing: number;
    key: string;
}

@Component({})
export default class SwiperList extends Vue {
    @Ref('swiper-wrap') private readonly swiperWrap!: HTMLElement;
    @Prop({
        type: Object,
        default: {
            width: 250,
            itemSpacing: 12,
            key: 'id',
        },
    })
    private readonly options!: SwiperListOptionVo;
    @Prop({
        default: [],
    })
    private data!: [];
    @Prop({
        type: String,
        default: ''
    })
    private idWrap!: string;
    private offestNum: number = 0; // 偏移个数
    private showNum: number = 0; // 当前页面可以展示的swiperSlide数量
    private widthWrap: number = 0; // 容器宽度
    private scroll: number = 0;  // 左移到展示出完整卡片
    private preWidth: number = 0;  // 去除滑动时滚动条造成的影响
    private mounted() {
        this.$nextTick(() => {
            const ro = new ResizeObserver((entries, observer) => {
                for (const entry of entries) {
                    const {width} = entry.contentRect;
                    // 滑动时滚动条出现造成影响,不重新获取宽度
                    if (Math.abs(width - this.preWidth) > 15 ) {
                        this.preWidth = width;
                        this.getWidth(width);
                    }
                }
            });
            ro.observe(this.swiperWrap);
        });
    }
    private getWidth(width: number): void {
        this.widthWrap = width;
        this.showNum = Math.floor(width / (this.options.width + this.options.itemSpacing));
        // swiper滑到最左边
        this.offestNum = 0;
        this.scroll = 0;
    }
    // 向左移动
    private goLeft(): void {
        if (this.offestNum < 0) {
            this.offestNum++;
            this.scroll = 0;
        }
    }
    // 向右移动
    private goRight(): void {
        if (this.offestNum > this.showNum - this.data.length) {
            this.offestNum--;
            const scroll: number = this.widthWrap - (this.data.length + this.offestNum) * (this.options.width + this.options.itemSpacing);
            if (scroll > 0) {
                this.scroll = this.options.itemSpacing + scroll;
            } else {
                this.scroll = 0;
            }
        }
    }
}
</script>

<style scoped lang="less">
// 自定义样式
.swiper-list::-webkit-scrollbar {
    display: none; /* Chrome Safari */
}
.swiper-wrap {
    overflow: hidden;
    position: relative;
    .button-wrap {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 60px;
        z-index: 20;
        background: rgba(255, 255, 255, 0.5);
        &.prev {
            left: 0;
        }
        &.next {
            right: 0;
        }
        img {
            cursor: pointer;
            width: 24px;
            height: 24px;
        }
    }
    .swiper-list {
        height: 100%;
        transition: margin-left 0.4s ease-in-out;
        &-item {
            flex: none;
            padding: 3px 0;
        }
    }
}
</style>

父组件使用

 <swiper-list :options="options" :data="cardList">
    <template slot-scope="slotProps">
        <card :cardInfo="slotProps.item"/>
    </template>
</swiper-list>
private options: SwiperListOptionVo = {
    width: 258,
    itemSpacing: 12,
    key: 'id',
};

目前局限

功能单一(估计就只能适应项目中需要的场景,手动尴尬)【像自动滚动,拖拽卡片滑动,延时移动速度等都没有实现,有时间需要多研究swiper实现的功能,以及实现方法】