固定宽高 + 露出下一张图片的一角
yangyunhe369.github.io/jQuery-Appl…
SegmentedSwiper
<script setup lang="ts">
import { ref } from 'vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Autoplay, Pagination } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/pagination';
const props = defineProps({
list: { type: Array, default: () => [] },
gap: { type: Number, default: 10 },
loop: { type: Boolean, default: true },
autoplay: { type: Boolean, default: true },
delay: { type: Number, default: 3000 }
});
const modules = [Pagination, Autoplay];
// 用于存储当前的进度百分比 (0 - 100)
const progressPercentage = ref(0);
// 1. 监听倒计时:计算进度并存入 CSS 变量
const onAutoplayTimeLeft = (s: any, time: number, progress: number) => {
// progress 是从 1 减少到 0,我们需要从 0 增加到 100
progressPercentage.value = (1 - progress) * 100;
};
// 2. 自定义指示器的渲染函数
// index: 索引, className: Swiper 默认生成的类名 (swiper-pagination-bullet)
const renderCustomBullet = (index: number, className: string) => {
// 返回自定义的 HTML 结构:一个底座(span),里面包一个填充层(i)
return `<span class="${className} custom-dash-bullet">
<i class="bullet-fill"></i>
</span>`;
};
</script>
<template>
<!--
将进度百分比绑定到 CSS 变量 --p 上
这样 CSS 就可以实时读取这个变量来改变宽度
-->
<div class="swiper-box" :style="{ '--p': progressPercentage + '%' }">
<swiper
:modules="modules"
:slides-per-view="'auto'"
:space-between="gap"
:loop="loop"
:autoplay="autoplay ? {
delay: delay,
disableOnInteraction: false
} : false"
:pagination="{
clickable: true,
renderBullet: renderCustomBullet /* 使用自定义渲染 */
}"
@autoplayTimeLeft="onAutoplayTimeLeft"
class="my-auto-swiper"
>
<swiper-slide
v-for="(item, index) in list"
:key="index"
class="auto-slide-item"
>
<slot name="item" :item="item" :index="index"></slot>
</swiper-slide>
</swiper>
</div>
</template>
<style scoped>
.swiper-box {
width: 100%;
position: relative;
/* 定义默认变量,防止报错 */
--p: 0%;
}
.auto-slide-item {
width: auto;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* --- 自定义指示器样式核心 --- */
/* 1. 穿透修改 Swiper 默认的分页器容器位置 */
:deep(.swiper-pagination) {
bottom: 10px; /* 距离底部距离 */
width: 100%;
display: flex;
justify-content: center;
gap: 8px; /* 指示器之间的间距 */
}
/* 2. 指示器底座 (灰色的条) */
:deep(.custom-dash-bullet) {
width: 30px; /* 每一段的宽度,根据设计图调整 */
height: 4px; /* 高度 */
background: rgba(255, 255, 255, 0.3); /* 未激活时的背景色 (半透明白) */
border-radius: 2px;
display: inline-block;
position: relative;
overflow: hidden; /* 保证内部填充不溢出 */
cursor: pointer;
opacity: 1; /* 覆盖 Swiper 默认的透明度 */
transition: width 0.3s; /* 可选:激活时变宽一点 */
}
/* 3. 内部填充层 (白色的条) */
:deep(.bullet-fill) {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #fff; /* 激活时的颜色 (纯白) */
width: 0%; /* 默认宽度为 0 */
border-radius: 2px;
}
/* 4. 关键:只有【当前激活】的指示器,其内部填充层的宽度才等于 --p */
:deep(.swiper-pagination-bullet-active .bullet-fill) {
width: var(--p);
}
/* 可选:如果你希望走过的指示器保持全白,未走的保持灰色 */
/*
:deep(.swiper-pagination-bullet-active ~ .custom-dash-bullet .bullet-fill) {
width: 0% !important;
}
*/
/* --- 新增:处理已播放的进度条 --- */
/*
原理:利用 CSS 的通用兄弟选择器 (~)
逻辑:
1. 找到当前激活的 bullet (.swiper-pagination-bullet-active)
2. 选中它【后面】所有的兄弟元素 (即未播放的)。
3. 但是我们要反过来思考:
Swiper 的 HTML 结构是 [1, 2, 3(active), 4, 5]。
CSS 很难直接选“前一个兄弟”。
所以我们通常有个更简单的 hack 方法:
让所有 bullet 默认是全白的 (width: 100%),
然后让 active 及其后面的 bullet 变成灰色或动态。
*/
/* 方案 B:简单粗暴法 (推荐) */
/* 默认所有芯都是满的 (白色) */
:deep(.bullet-fill) {
width: 100%;
transition: width 0s; /* 瞬间变白 */
}
/* 激活状态:宽度由变量控制 */
:deep(.swiper-pagination-bullet-active .bullet-fill) {
width: var(--p);
}
/* 激活状态【之后】的所有兄弟:宽度为 0 (变灰) */
:deep(.swiper-pagination-bullet-active ~ .custom-dash-bullet .bullet-fill) {
width: 0%;
}
</style>
父组件
<script setup lang="ts">
import { ref } from 'vue';
// 1. 引入你刚才封装的组件
import SegmentedSwiper from '@/components/SegmentedSwiper.vue';
// 2. 准备数据
const bannerList = ref([
{ id: 1, title: '热门活动', image: 'https://placehold.co/600x400/1989fa/FFF?text=Hot' },
{ id: 2, title: '新用户礼包', image: 'https://placehold.co/600x400/07c160/FFF?text=New' },
{ id: 3, title: '限时折扣', image: 'https://placehold.co/600x400/ee0a24/FFF?text=Sale' },
{ id: 4, title: '会员专享', image: 'https://placehold.co/600x400/7232dd/FFF?text=VIP' },
]);
</script>
<template>
<div class="home-page">
<h2>倒计时进度条轮播</h2>
<!-- Nuxt 中建议包裹 ClientOnly 防止服务端渲染导致的水合不匹配 -->
<ClientOnly>
<SegmentedSwiper
:list="bannerList"
:gap="12"
:autoplay="true"
:delay="3000"
loop
>
<!--
3. 使用插槽 (#item) 自定义每一页的内容
解构出 { item, index } 供使用
-->
<template #item="{ item, index }">
<!-- 这里的 div 决定了轮播卡片的大小 -->
<div class="banner-card">
<img :src="item.image" :alt="item.title" />
<div class="info">
<span>{{ index + 1 }}</span>
<p>{{ item.title }}</p>
</div>
</div>
</template>
</SegmentedSwiper>
</ClientOnly>
</div>
</template>
<style scoped>
.home-page {
padding: 20px;
background-color: #000; /* 配合白色进度条,深色背景更好看 */
min-height: 100vh;
color: #fff;
}
/*
【核心关键点】
必须指定卡片的宽和高!
Swiper 设置了 slidesPerView: 'auto',它会根据这个类的宽度来排版。
*/
.banner-card {
width: 300px; /* 固定宽度,实现露出下一张的效果 */
height: 180px; /* 固定高度 */
border-radius: 12px;
overflow: hidden;
position: relative;
background-color: #333;
}
.banner-card img {
width: 100%;
height: 100%;
object-fit: cover;
}
.banner-card .info {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 10px;
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
pointer-events: none; /* 防止遮挡点击 */
}
</style>