功能:
-
自動循環播放(到達末尾后回到第一張)、可設置切換間隔時間(
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結合城市樓宇光伏、社區微電網等場景,清潔能源可深度融入城市基礎設施,形成“自給+共享”的能源網絡。",}
])