功能:
-
自动循环播放(到达末尾后回到第一张)、可设置切换间隔时间(
interval
属性) -
左右导航按钮(可自定义显示/隐藏)
-
点击底部指示器跳转到指定幻灯片、且位置可调(轮播图内部/外部)
-
鼠标拖拽滑动(PC端)
-
触摸滑动(移动端适配)
-
支持暂停/继续自动播放(
autoPlay
控制) -
用户交互(拖拽/点击按钮)时自动暂停轮播
-
点击幻灯片可跳转页面(这里是跳转到详情)
封装好了,直接用就可以:components/Carousel.vue:
<template><div class="carousel-container"><!-- 左侧导航按钮 --><button class="nav-button prev" @click="prevSlide" v-if="showNavButtons">‹</button><div class="carousel-viewport" ref="viewport"@mousedown="startDrag"@mousemove="handleDrag"@mouseup="endDrag"@mouseleave="endDrag"@touchstart="startDrag"@touchmove="handleDrag"@touchend="endDrag"><div class="carousel-track" :style="trackStyle"><div class="slide" v-for="(item, index) in items" :key="item.id"@click="handleSlideClick(index)"><img :src="item.cover" :alt="item.title"><div class="slide-title" :class="{ 'active': currentIndex === index }">{{ item.categoryTag }}</div></div></div></div><!-- 右侧导航按钮 --><button class="nav-button next" @click="nextSlide" v-if="showNavButtons">›</button><div class="indicators" :class="{ 'inside': indicatorsInside }"><span v-for="(item, index) in items" :key="item.id":class="{ 'active': currentIndex === index }"@click="goToSlide(index)"></span></div><!-- 标题和摘要显示区域 --><div class="carousel-caption"><div class="carousel-title">{{ currentItem.title }}</div><div class="carousel-summary">{{ currentItem.summary }}</div></div></div>
</template><script setup>
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';const props = defineProps({items: { // 父组件传过来的数据,下面会有模拟数据type: Array,required: true,default: () => []},interval: { // 控制自动轮播的切换时间间隔type: Number,default: 3000},autoPlay: { // 是否自动切换type: Boolean,default: true},initialIndex: { // 指定轮播图初始显示的第几张幻灯片,默认显示第 1 张type: Number,default: 0},indicatorsInside: { // 控制指示器轮播区域内部/外部显示,true为在外部显示,false为在内部显示default: false},showNavButtons: { // 控制左右按钮显示,这里默认隐藏type: Boolean,default: false}
});const currentIndex = ref(props.initialIndex);
const viewport = ref(null);
let autoPlayTimer = null;// 拖拽相关状态
const isDragging = ref(false);
const startPos = ref(0);
const currentTranslate = ref(0);
const prevTranslate = ref(0);
const animationId = ref(null);
const dragDistance = ref(0);// 计算当前显示的item
const currentItem = computed(() => {return props.items[currentIndex.value] || {};
});// 图片宽度配置
const slideConfig = {fullWidth: 680,visiblePart: 320,gap: 20
};// 计算轨道偏移量
const trackStyle = computed(() => {if (isDragging.value) {return {transform: `translateX(${currentTranslate.value}px)`,transition: 'none'};}const offset = currentIndex.value * -(slideConfig.fullWidth + slideConfig.gap);return {transform: `translateX(${offset}px)`,transition: 'transform 0.5s ease'};
});// 开始拖拽
const startDrag = (e) => {stopAutoPlay();isDragging.value = true;startPos.value = getPositionX(e);prevTranslate.value = currentIndex.value * -(slideConfig.fullWidth + slideConfig.gap);currentTranslate.value = prevTranslate.value;dragDistance.value = 0;cancelAnimationFrame(animationId.value);
};// 处理拖拽
const handleDrag = (e) => {if (!isDragging.value) return;const currentPosition = getPositionX(e);const diff = currentPosition - startPos.value;currentTranslate.value = prevTranslate.value + diff;dragDistance.value = Math.abs(diff);
};// 结束拖拽
const endDrag = () => {if (!isDragging.value) return;const movedBy = currentTranslate.value - prevTranslate.value;// 如果拖动距离超过100px,则切换幻灯片if (movedBy < -100 && currentIndex.value < props.items.length - 1) {currentIndex.value += 1;} else if (movedBy > 100 && currentIndex.value > 0) {currentIndex.value -= 1;}isDragging.value = false;resetAutoPlay();
};// 获取当前位置
const getPositionX = (e) => {return e.type.includes('mouse') ? e.pageX : e.touches[0].clientX;
};// 处理幻灯片点击
const handleSlideClick = (index) => {// 如果拖动距离大于10px,则不触发点击事件if (dragDistance.value > 10) return;goToDetail(props.items[index].id);
};// 切换上一张
const prevSlide = () => {currentIndex.value = (currentIndex.value - 1 + props.items.length) % props.items.length;resetAutoPlay();
};// 切换下一张
const nextSlide = () => {currentIndex.value = (currentIndex.value + 1) % props.items.length;resetAutoPlay();
};// 跳转到指定图片
const goToSlide = (index) => {currentIndex.value = index;resetAutoPlay();
};// 自动播放控制
const startAutoPlay = () => {if (!props.autoPlay) return;stopAutoPlay();autoPlayTimer = setInterval(() => {nextSlide();}, props.interval);
};const stopAutoPlay = () => {if (autoPlayTimer) {clearInterval(autoPlayTimer);autoPlayTimer = null;}
};const resetAutoPlay = () => {if (props.autoPlay) {stopAutoPlay();startAutoPlay();}
};// 监听autoPlay变化
watch(() => props.autoPlay, (newVal) => {if (newVal) {startAutoPlay();} else {stopAutoPlay();}
});// 监听initialIndex变化
watch(() => props.initialIndex, (newVal) => {if (newVal >= 0 && newVal < props.items.length) {currentIndex.value = newVal;}
});const goToDetail = (id) => {navigateTo({ path: `/case/${id}.html` });
};onMounted(() => {startAutoPlay();
});onBeforeUnmount(() => {stopAutoPlay();cancelAnimationFrame(animationId.value);
});
</script><style scoped>
.carousel-container {position: relative;width: 100%;max-width: 1280px;margin: 0 auto;height: 400px;user-select: none;
}.carousel-viewport {position: relative;width: 100%;height: 100%;overflow: hidden;cursor: grab;
}.carousel-viewport:active {cursor: grabbing;
}.carousel-track {display: flex;height: 100%;padding: 0 calc(50% - 340px);will-change: transform;
}.slide {flex: 0 0 680px;height: 100%;margin-right: 20px;position: relative;cursor: pointer;touch-action: pan-y;
}.slide img {width: 100%;height: 100%;object-fit: cover;border-radius: 10px;pointer-events: none;
}.slide-title {position: absolute;bottom: 0;right: 0;background: rgba(0, 0, 0, 0.5);color: white;text-align: center;padding: 8px 17px;border-radius: 10px 0px 10px 0px;font-size: 14px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;transition: opacity 0.3s ease;
}/* 指示点样式 - 默认在外部 */
.indicators {position: absolute;bottom: 20px;left: 0;right: 0;display: flex;justify-content: center;gap: 10px;z-index: 20;
}/* 指示点在内部的样式 */
.indicators.inside {bottom: -30px;
}.indicators span {width: 10px;height: 10px;border-radius: 50%;background-color: #E0E0E0;cursor: pointer;transition: all 0.3s ease;
}.indicators span.active {background-color: #0A53B2;transform: scale(1.2);
}/* 标题和摘要样式 */
.carousel-caption {margin-top: 70px;text-align: center;padding: 0 20px;text-align: center;
}.carousel-title {font-size: 26px;font-weight: bold;margin-bottom: 16px;color: #333;
}.carousel-summary {font-size: 16px;color: #808080;line-height: 1.5;
}/* 导航按钮样式 */
.nav-button {position: absolute;top: 50%;transform: translateY(-50%);background: rgba(0, 0, 0, 0.5);color: white;border: none;width: 40px;height: 40px;border-radius: 50%;font-size: 20px;cursor: pointer;z-index: 20;display: flex;align-items: center;justify-content: center;
}.nav-button:hover {background: rgba(0, 0, 0, 0.8);
}.prev {left: 20px;
}.next {right: 20px;
}/* 响应式调整 */
@media (max-width: 768px) {.nav-button {width: 30px;height: 30px;font-size: 16px;}.prev {left: 10px;}.next {right: 10px;}
}
</style>
父组件使用:
<Carousel :items="list" :interval="3000" :autoPlay="true" :initialIndex="1" :indicatorsInside="true"/>
list 模拟数据可以用这个:
const list = ref([{"id": 303,"categoryTag": "政府办公","title": "用智能化解决方案,助力政府完成办公基础设备搭建,系统管理全面升级","cover": "http://szdxyp.com/images/c02f612c-dd69-47ee-aafd-86aaa73df208.png","summary": "用智能化解决方案,简介xxxxxxxxxxxxxxxxxxxx",},{"id": 304,"categoryTag": "金融风控","title": "财富基石:金融数据与资产积累","cover": "http://szdxyp.com/images/1d128ffa-f4f0-485e-878c-86a708769a19.png","summary": "用智能化金融方案,简介xxxxxxxxxxxxxxxxxxxx硬币象征财富,图表代表金融数据,整体传达了金融数据在财富积累中的关键作用。",},{"id": 305,"categoryTag": "政府办公","title": "数字创想:代码世界的团队协作","cover": "http://szdxyp.com/images/080f8bdc-9133-4aaa-a34a-d9b965c23c24.png","summary": "用智能化金融方案,简介xxxxxxxxxxxxxxxxxxxx突出了编程协作在构建数字未来中的基础性作用,展示了团队合作的重要性。",},{"id": 306,"categoryTag": "智慧教育","title": "《协同共生:城市脉动与绿色能源的双向赋能》","cover": "http://szdxyp.com/images/3d670ed1-f68b-48d7-8bb6-d59d7d477846.png","summary": "展现出城市化进程与清洁能源发展的共生关系。左侧图片聚焦现代化城市的楼群林立与繁华交通,象征经济与技术的高度聚合;右侧呈现风力发电装置与太阳能板的能量转换场景,凸显对可再生能源的实践追求。两者结合,揭示了在全球化与生态危机并存的当下,城市与自然的平衡共生成为可持续发展的核心命题",},{"id": 307,"categoryTag": "金融风控","title": "清洁能源:生态可持续的技术突围","cover": "http://szdxyp.com/images/f4226060-347a-45d5-a379-3716189bc344.png","summary": "可再生能源实践:\n右侧风能、太阳能的规模化应用,体现了对化石能源的替代性突破,减少碳排放的同时,提高能源安全性与区域自主性。\n分布式能源趋势:\n结合城市楼宇光伏、社区微电网等场景,清洁能源可深度融入城市基础设施,形成“自给+共享”的能源网络。",}
])