3DSwiper 好看的走马灯轮播图
代码来源
vue3写法,复制即用
<template><div class="swipe-box"><div id="swipe3D"><div class="nav-btn prev" @click="rotate(-1,true)">❮</div><div class="nav-btn next" @click="rotate(1,true)">❯</div><div class="spinner"></div><div class="indicators"></div></div></div>
</template>
<script setup>
// 配置参数
const config = reactive({maxWidth: '180px',speed: 10000, // 轮播速度multiple: 1, // 鼠标悬停放大倍速autoPlay: true, // 是否自动轮播imgList: ["https://img0.baidu.com/it/u=1446729335,4267600834&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img2.baidu.com/it/u=2922694860,2270800253&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img0.baidu.com/it/u=595403291,2269048245&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img0.baidu.com/it/u=1164365158,2722332607&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img2.baidu.com/it/u=9876582,1040648435&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img1.baidu.com/it/u=2172054385,2684839842&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img1.baidu.com/it/u=1879107733,3573789717&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img2.baidu.com/it/u=1720323410,218932174&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img1.baidu.com/it/u=2479779032,1379361694&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img1.baidu.com/it/u=537208727,895387248&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img1.baidu.com/it/u=3310720258,943543712&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img2.baidu.com/it/u=2165957654,3023194084&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img0.baidu.com/it/u=1280320387,1585665801&fm=253&fmt=auto&app=138&f=JPEG?w=368&h=368","https://img0.baidu.com/it/u=1403512971,1919400898&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500","https://img0.baidu.com/it/u=707869343,3146161581&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500",],
})
// 初始化状态
const angle = ref(0)
const currentIndex = ref(0)
const mouseHover = ref(false)
const autoPlayInterval = ref()
const totalImages = reactive(config.imgList.length)
const degreesPerImage = reactive(360 / totalImages)
const spinner = ref()
onMounted(() => {spinner.value = document.querySelector('.spinner')const indicators = document.querySelector('.indicators')// 动态创建图片元素config.imgList.forEach((src, index) => {const img = document.createElement('img')img.src = srcimg.dataset.index = index// 计算每个图片的旋转角度const rotateAngle = -degreesPerImage * indeximg.style.transform = `rotateY(${rotateAngle}deg) translateZ(450px)`img.addEventListener('mouseenter', handleMouseEnter)img.addEventListener('mouseleave', handleMouseLeave)img.addEventListener('click', () => handleImageClick((totalImages - index) % totalImages))spinner.value.appendChild(img)// 创建指示器const indicator = document.createElement('div')indicator.className = 'indicator' + (index === 0 ? ' active' : '')indicator.dataset.index = indexindicator.addEventListener('click', () => goToSlide(index))indicators.appendChild(indicator)})// 响应窗口大小变化window.addEventListener('resize', updateSlide)// 启动自动轮播startAutoPlay()
})// 事件处理
const handleMouseEnter = (e) => {const img = e.targetimg.style.transform = `${img.style.transform} scale(${config.multiple})`img.style.boxShadow = '0 15px 30px rgba(0, 0, 0, 0.25)'img.style.zIndex = '10'mouseHover.value = truestopAutoPlay()
}const handleMouseLeave = (e) => {const img = e.targetconst rotateAngle = -degreesPerImage * img.dataset.indeximg.style.transform = `rotateY(${rotateAngle}deg) translateZ(450px)`img.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.15)'img.style.zIndex = '1'mouseHover.value = falsestartAutoPlay()
}const handleImageClick = (index) => {stopAutoPlay()goToSlide(index)
}// 控制轮播
const rotate = (direction, flag = false) => {if (flag) {stopAutoPlay()}// 计算目标索引const newIndex = (currentIndex.value + direction + totalImages) % totalImages// 计算最短旋转路径let angleDiff = newIndex - currentIndex.value// 确保旋转方向是最短路径if (angleDiff > totalImages / 2) {angleDiff -= totalImages} else if (angleDiff < -totalImages / 2) {angleDiff += totalImages}// 更新当前索引和角度currentIndex.value = newIndexangle.value += angleDiff * degreesPerImageupdateSlide(flag)
}const goToSlide = (index) => {stopAutoPlay()// 计算最短旋转路径let angleDiff = index - currentIndex.value// 确保旋转方向是最短路径if (angleDiff > totalImages / 2) {angleDiff -= totalImages} else if (angleDiff < -totalImages / 2) {angleDiff += totalImages}// 更新当前索引和角度currentIndex.value = indexangle.value += angleDiff * degreesPerImageupdateSlide(true)
}const updateSlide = (flag = false) => {// 使用负角度进行旋转,确保顺时针旋转spinner.value.style.transform = `rotateY(${-angle.value}deg)`// 更新指示器状态document.querySelectorAll('.indicator').forEach((indicator, i) => {if (i === currentIndex.value) {indicator.classList.add('active')} else {indicator.classList.remove('active')}})if (flag) {startAutoPlay()}
}const startAutoPlay = () => {if (mouseHover.value || !config.autoPlay) returnautoPlayInterval.value = setInterval(() => {if (!mouseHover.value) {rotate(1)}}, config.speed)
}const stopAutoPlay = () => {clearInterval(autoPlayInterval.value)autoPlayInterval.value = null
}// 键盘控制
// window.addEventListener("keydown", (e) => {
// if (e.key === "ArrowLeft") rotate(-1, true);
// if (e.key === "ArrowRight") rotate(1, true);
// });
</script>
<style lang="scss">
.swipe-box {margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;min-height: 400px;font-family: Arial, sans-serif;width: 1200px;#swipe3D {perspective: 1200px;width: 100%;max-width: 1200px;overflow: hidden;margin: 0 auto;padding: 50px 0;position: relative;}.spinner {transform-style: preserve-3d;transform-origin: 50% 50% calc(-1 * 450px);height: 320px;transition: transform 0.8s cubic-bezier(0.23, 1, 0.32, 1);position: relative;img {position: absolute;left: 50%;top: 50%;width: 200px;height: auto;object-fit: cover;transform-origin: 50% 50% calc(-1 * 450px);transform-style: preserve-3d;transition: all 0.3s ease-out;margin-left: calc(-1 * 200px / 2);margin-top: -100px;border-radius: 8px;box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);}}/* 控制轮播图的指示器 */.indicators {display: flex;justify-content: center;margin-top: 30px;}.indicator {width: 10px;height: 10px;border-radius: 50%;background-color: rgba(0, 0, 0, 0.2);margin: 0 5px;cursor: pointer;transition: background-color 0.3s;}.indicator.active {background-color: rgba(0, 0, 0, 0.6);}/* 导航按钮 */.nav-btn {position: absolute;top: 50%;transform: translateY(-50%);width: 40px;height: 40px;border-radius: 50%;display: flex;align-items: center;justify-content: center;background-color: rgba(255, 255, 255, 0.8);cursor: pointer;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);transition: all 0.3s;z-index: 10;}.nav-btn:hover {background-color: white;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);}.nav-btn.prev {left: 20px;}.nav-btn.next {right: 20px;}
}
</style>
成果
ps:更新图片大小时自行调整