背景
业务开发中需要一个像swiper一样展示的效果,没有要求自动来回轮播,功能比较简单,使用swiper包有比较大,所以准备自己简单实现一个
实现思路
计算展示框宽度,以及可以展示的卡片数量。可以存放时,没有左右移动按钮,存放不下时,默认展示可以存放的卡片,每次移动一个卡片宽度的距离,实现卡片的移动展示,最后一个卡片移动到完全展示即可
注意要点与解决
- 不同电脑下页面展示框的宽度不一样,需要实时监听计算 解决方案:使用浏览器提供的ResizeObserver Api,可以实时监听展示容器的宽度是否发生变化(刚开始监听的浏览器窗口变化,获取dom宽度,存在浏览器窗口不发生变化时,组件无法实时获取宽度【即弹框内等其他动态生成的dom内无法使用】)
- 一个组件多处使用,确保不受影响 解决方案:使用 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实现的功能,以及实现方法】