SwiperJS时间轴多交互功能冲突问题求助
时间轴与Swiper联动冲突解决方案
问题背景
使用SwiperJS实现时间轴联动功能,期望实现三个交互:
- 点击时间轴上的
.timespan,切换到对应swiper-slide,同时将该.timespan设为active并滚动到视口中央 - 滚动时间轴容器时,自动将视口中央的
.timespan设为active并切换对应slide - 拖拽swiper切换slide时,同步将对应
.timespan设为active并滚动到视口中央
但目前三个功能的函数互相冲突,setActiveClass会限制点击或拖拽切换slide的操作。
冲突原因
- 点击或拖拽触发slide切换时,
scrollToTimespan会滚动时间轴容器,触发scroll事件调用setActiveClass,后者又会强制切换回当前视口中央对应的slide,形成循环 slideChange事件中的首尾slide判断(!this.isBeginning && !this.isEnd)导致首尾slide无法触发联动
解决方案
通过添加全局状态锁区分操作来源,避免循环触发,同时修复首尾slide的联动逻辑:
修改步骤
- 添加全局变量
isUserInitiated标记是否为用户主动操作(点击/拖拽) - 在点击事件和
slideChange事件中设置isUserInitiated = true,操作完成后延迟重置 - 修改
setActiveClass,仅当非用户主动操作时才切换slide - 移除
slideChange中的首尾slide限制 - 优化DOM查询,避免重复获取元素
修改后的完整代码
JavaScript部分
/* --------------- Timeline Swiper --------------- */ let isUserInitiated = false; // 标记用户主动操作 const timelineSwiper = new Swiper(".timeline-swiper", { loop: false, autoplay: false, on: { init: function () { const timeline = document.querySelectorAll(".timespan"); timeline.forEach(function (timespan) { timespan.addEventListener("click", function () { isUserInitiated = true; // 移除所有active类 timeline.forEach(item => item.classList.remove("active")); // 给当前点击项加active timespan.classList.add("active"); // 滚动到当前项 scrollToTimespan(timespan); // 切换对应slide const slideIndex = parseInt(timespan.dataset.slideIndex); timelineSwiper.slideTo(slideIndex); // 延迟重置状态,避免scroll事件触发时误判 setTimeout(() => isUserInitiated = false, 300); }); }); }, slideChange: function () { isUserInitiated = true; const timeline = document.querySelectorAll(".timespan"); const activeSlideIndex = timelineSwiper.activeIndex; // 移除所有active类 timeline.forEach(item => item.classList.remove("active")); // 给对应timespan加active timeline[activeSlideIndex].classList.add("active"); // 滚动到对应timespan scrollToTimespan(timeline[activeSlideIndex]); setTimeout(() => isUserInitiated = false, 300); }, }, }); /* --------------- Timeline Scroll --------------- */ const timespans = document.querySelectorAll(".timespan"); const scrollContainer = document.querySelector(".about .container"); let scrolling = false; // 判断元素是否在视口中央 function isElementInViewport(element) { const rect = element.getBoundingClientRect(); const viewportHeight = window.innerHeight || document.documentElement.clientHeight; const middleViewport = viewportHeight / 2; return rect.top <= middleViewport && rect.bottom >= middleViewport; } // 移除所有timespan的active类 function removeActiveClass() { timespans.forEach(timespan => timespan.classList.remove("active")); } // 设置视口中央的timespan为active,仅非用户主动操作时切换slide function setActiveClass() { removeActiveClass(); timespans.forEach(timespan => { if (isElementInViewport(timespan)) { timespan.classList.add("active"); if (!isUserInitiated) { const slideIndex = parseInt(timespan.dataset.slideIndex); timelineSwiper.slideTo(slideIndex); } } }); } // 滚动到指定timespan function scrollToTimespan(timespan) { timespan.scrollIntoView({ behavior: "smooth", block: "center", inline: "center", }); } // 初始化和滚动监听 window.addEventListener("load", setActiveClass); scrollContainer.addEventListener("scroll", function () { if (!scrolling) { scrolling = true; window.requestAnimationFrame(() => { setActiveClass(); scrolling = false; }); } });
CSS部分
.section { position: relative; width: 100%; font-family: "Poppins", sans-serif; overflow: hidden; } .about .container { position: relative; overflow: hidden; width: 100%; padding: 0 1.5rem; display: grid; grid-template-columns: 1.05fr 1fr; overflow-y: scroll; height: 100vh; scroll-snap-type: y mandatory; /* 滚动吸附 */ scrollbar-width: thin; /* Firefox滚动条样式 */ -ms-overflow-style: none; /* IE/Edge滚动条样式 */ } .about .container::-webkit-scrollbar { display: none; /* Chrome/Safari滚动条隐藏 */ } .about-timeline { margin: 500px auto; } .timespan { max-width: 450px; min-height: 100px; display: flex; flex-direction: column; justify-content: center; text-align: center; padding: 0 4rem; cursor: pointer; border: 2px solid black; border-radius: 20px; scroll-snap-align: center; /* 吸附到视口中央 */ scroll-snap-stop: always; /* 强制吸附 */ } .line { position: relative; margin: auto; width: 2px; height: 50px; background-color: black; } .timespan.active { background-color: rgba(0, 255, 0, 0.1); } .about-info { height: 100%; margin: 4rem; } .timeline-swiper .swiper-wrapper { display: flex; align-items: center; width: 300px; } .info-container { position: sticky; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; align-items: center; border: 2px solid black; background-color: rgba(0, 255, 0, 0.1); border-radius: 20px; } .about .text { margin: 3rem 2rem; }
HTML部分
<!-- Swiper样式 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.css" /> <section class="about section" id="about"> <div class="container"> <div class="about-timeline"> <div class="timespan active" data-slide-index="0">Timespan1</div> <div class="line"></div> <div class="timespan" data-slide-index="1">Timespan2</div> <div class="line"></div> <div class="timespan" data-slide-index="2">Timespan3</div> <div class="line"></div> <div class="timespan" data-slide-index="3">Timespan4</div> <div class="line"></div> <div class="timespan" data-slide-index="4">Timespan5</div> <div class="line"></div> <div class="timespan" data-slide-index="5">Timespan6</div> <div class="line"></div> </div> <div class="about-info"> <div class="info-container"> <div class="swiper timeline-swiper"> <div class="swiper-wrapper"> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> <div class="swiper-slide"> <p class="text"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error rerum eaque, praesentium, fugiat repudiandae dolorem suscipit consequatur neque nisi harum delectus exercitationem odit expedita tempora ipsum facere, rem hic laudantium? </p> </div> </div> </div> </div> </div> </div> </section> <!-- Swiper脚本 --> <script src="https://cdn.jsdelivr.net/npm/swiper@10/swiper-bundle.min.js"></script>
内容的提问来源于stack exchange,提问作者Louis Eiden
相关产品推荐
相关产品推荐

