创建 Banner.vue
<template>
<div class="banner" :class="round ? 'round' : '' ">
<div class="carousel">
<div class="carousel-list" :style="listStyle">
<!-- 添加最后一张图片到开头 -->
<div class="carousel-item" :key="'last'">
<img :src="items[items.length - 1].image" :alt="items[items.length - 1].alt">
<div class="banner-overlay" v-if="overlay"></div>
</div>
<!-- 正常轮播项 -->
<div class="carousel-item" v-for="(item, index) in items" :key="index">
<img :src="item.image" :alt="item.alt">
<div class="banner-overlay" v-if="overlay"></div>
</div>
<!-- 添加第一张图片到末尾 -->
<div class="carousel-item" :key="'first'">
<img :src="items[0].image" :alt="items[0].alt">
<div class="banner-overlay" v-if="overlay"></div>
</div>
</div>
<!-- 左箭头按钮 -->
<template v-if="showArrows">
<button class="carousel-arrow prev" @click="prev">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="24px" height="24px">
<path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/>
</svg>
</button>
<!-- 右箭头按钮 -->
<button class="carousel-arrow next" @click="next">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="24px" height="24px">
<path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
</svg>
</button>
</template>
<!-- 指示器 -->
<div class="indicators">
<span v-for="(item, index) in items" :key="index" :class="{ active: currentRealIndex === index }"
@click="goTo(index)"
></span>
</div>
</div>
</div>
</template>
<script setup>
import {ref, computed, onMounted, onBeforeUnmount} from 'vue'
const props = defineProps({
items: {
type: Array,
required: true,
validator: value => value.every(item => 'image' in item && 'title' in item)
},
interval: {
type: Number,
default: 3000
},
autoplay: {
type: Boolean,
default: true
},
showArrows: {
type: Boolean,
default: true
},
round:{
type: Boolean,
default: true
},
overlay:{
type: Boolean,
default: false
}
})
const currentIndex = ref(1) // 从1开始,因为0是额外添加的最后一张图片
const currentRealIndex = ref(0) // 用于指示器显示的实际索引
const timer = ref(null)
const isTransitioning = ref(false) // 是否正在过渡中
// 计算轮播图列表样式
const listStyle = computed(() => {
return {
transform: `translateX(-${currentIndex.value * 100}%)`,
transition: isTransitioning.value ? `transform ${transitionDuration.value / 1000}s ease` : 'none'
}
})
// 配置项
const transitionDuration = ref(500) // 过渡动画时间(ms)
// 切换到下一张
const next = () => {
if (isTransitioning.value) return
isTransitioning.value = true
currentIndex.value++
currentRealIndex.value = (currentRealIndex.value + 1) % props.items.length
// 如果到达最后一张额外添加的图片(即第一张的副本)
if (currentIndex.value === props.items.length + 1) {
setTimeout(() => {
isTransitioning.value = false
currentIndex.value = 1 // 无缝跳转到真实的第一张
}, transitionDuration.value)
} else {
setTimeout(() => {
isTransitioning.value = false
}, transitionDuration.value)
}
resetTimer()
}
// 切换到上一张
const prev = () => {
if (isTransitioning.value) return
isTransitioning.value = true
currentIndex.value--
currentRealIndex.value = (currentRealIndex.value - 1 + props.items.length) % props.items.length
// 如果到达第一张额外添加的图片(即最后一张的副本)
if (currentIndex.value === 0) {
setTimeout(() => {
isTransitioning.value = false
currentIndex.value = props.items.length // 无缝跳转到真实的最后一张
}, transitionDuration.value)
} else {
setTimeout(() => {
isTransitioning.value = false
}, transitionDuration.value)
}
resetTimer()
}
// 跳转到指定索引
const goTo = (index) => {
if (isTransitioning.value) return
isTransitioning.value = true
currentIndex.value = index + 1
currentRealIndex.value = index
setTimeout(() => {
isTransitioning.value = false
}, transitionDuration.value)
resetTimer()
}
// 重置定时器
const resetTimer = () => {
if (timer.value) {
clearInterval(timer.value)
}
if (props.autoplay) {
timer.value = setInterval(next, props.interval)
}
}
// 生命周期钩子
onMounted(() => {
resetTimer()
})
onBeforeUnmount(() => {
if (timer.value) {
clearInterval(timer.value)
}
})
</script>
<style scoped lang="scss">
.round{
border-radius:10px;
};
.banner {
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
.carousel {
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
.carousel-list {
display: flex;
height: 100%;
width: 100%;
.carousel-item {
flex: 0 0 100%;
height: 100%;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
position: relative;
z-index: 1;
}
}
}
.carousel-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
background-color: rgba(0, 0, 0, 0.2);
border: none;
border-radius: 50%;
color: white;
cursor: pointer;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:hover {
background-color: rgba(0, 0, 0, 0.6);
}
&.prev {
left: 20px;
}
&.next {
right: 20px;
}
svg {
width: 24px;
height: 24px;
}
}
.indicators {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
display: flex;
span {
width: 12px;
height: 12px;
margin: 0 6px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
cursor: pointer;
transition: all 0.3s ease;
&.active {
background-color: #fff;
transform: scale(1.2);
}
}
}
}
.banner-overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40%;
background: linear-gradient(
to top,
rgba(14, 59, 130, 0.71) 0%,
rgba(14, 59, 130, 0.3) 70%,
transparent 100%
);
z-index: 2;
}
}
/* 响应式设计 */
//@media (max-width: 1200px) {
// .banner {
// height: 600px;
// }
//}
//@media (max-width: 992px) {
// .banner {
// height: 550px;
// }
//}
@media (max-width: 768px) {
.banner {
//height: 500px;
.carousel {
.carousel-arrow {
width: 36px;
height: 36px;
svg {
width: 20px;
height: 20px;
}
&.prev {
left: 10px;
}
&.next {
right: 10px;
}
}
.indicators {
bottom: 20px;
span {
width: 10px;
height: 10px;
margin: 0 4px;
}
}
}
}
}
@media (max-width: 576px) {
.banner {
//height: 450px;
.carousel {
.carousel-arrow {
width: 32px;
height: 32px;
svg {
width: 18px;
height: 18px;
}
}
.indicators {
bottom: 15px;
span {
width: 8px;
height: 8px;
}
}
}
}
}
</style>
使用
<div style="height: 300px;margin: 0 auto;">
<Banner
:items="banners"
:interval="2000"
:showArrows="true"
:round="false"
:overlay="true"
/>
</div>
</template>
<script setup>
import Banner from "@/components/banner/banner.vue";
const banners = [
{
image: 'https://自己选择.jpg',
alt: '轮播图1',
},
{
image: 'https://自己选择.jpg',
alt: '轮播图2',
},
{
image: 'https://自己选择.jpg',
alt: '轮播图3',
}
]
</script>
本文链接:https://www.xlsir.cn/web_list/20.html