You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiperJS时间轴多交互功能冲突问题求助

时间轴与Swiper联动冲突解决方案

问题背景

使用SwiperJS实现时间轴联动功能,期望实现三个交互:

  • 点击时间轴上的.timespan,切换到对应swiper-slide,同时将该.timespan设为active并滚动到视口中央
  • 滚动时间轴容器时,自动将视口中央的.timespan设为active并切换对应slide
  • 拖拽swiper切换slide时,同步将对应.timespan设为active并滚动到视口中央

但目前三个功能的函数互相冲突,setActiveClass会限制点击或拖拽切换slide的操作。

冲突原因

  1. 点击或拖拽触发slide切换时,scrollToTimespan会滚动时间轴容器,触发scroll事件调用setActiveClass,后者又会强制切换回当前视口中央对应的slide,形成循环
  2. slideChange事件中的首尾slide判断(!this.isBeginning && !this.isEnd)导致首尾slide无法触发联动

解决方案

通过添加全局状态锁区分操作来源,避免循环触发,同时修复首尾slide的联动逻辑:

修改步骤

  1. 添加全局变量isUserInitiated标记是否为用户主动操作(点击/拖拽)
  2. 在点击事件和slideChange事件中设置isUserInitiated = true,操作完成后延迟重置
  3. 修改setActiveClass,仅当非用户主动操作时才切换slide
  4. 移除slideChange中的首尾slide限制
  5. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:44:54