You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

滚动动画在鼠标滚轮正常,笔记本触控板自动滚动异常的问题求助

滚动动画在鼠标滚轮正常,笔记本触控板自动滚动异常的问题求助

我完全理解你遇到的困扰——触控板的滚动机制和鼠标滚轮差异很大:鼠标滚轮的wheel事件是离散的、每次触发对应一个明确的滚动步长,而触控板会连续触发大量高频的wheel事件来模拟平滑滚动,这直接导致你的scrollToSection被疯狂调用,出现滚动跳步、卡顿或者失控的情况。

你之前尝试的方向是对的,但需要更精准的处理逻辑,下面是几个针对性的修复方案,结合你的代码给出具体修改:

方案一:实现智能防抖+节流,限制滚动触发频率

固定的setTimeout防抖不够灵活,我们可以结合时间戳节流状态锁,同时忽略触控板产生的微小deltaY事件(这类事件是触控板平滑滚动的“中间帧”,不需要响应):

修改你的JavaScript部分:

document.addEventListener("DOMContentLoaded", function () {
  const container = document.querySelector(".container");
  const sections = document.querySelectorAll(".image-section");
  let currentIndex = 0;
  let isScrolling = false;
  let lastScrollTime = 0;
  const SCROLL_COOLDOWN = 800; // 滚动冷却时间,可根据你的动画时长调整
  const MIN_DELTA_Y = 50; // 忽略微小的deltaY事件(触控板的中间滚动帧)

  function scrollToSection(index) {
    if (index < 0 || index >= sections.length || isScrolling) return;
    isScrolling = true;
    sections[index].scrollIntoView({ behavior: "smooth" });
    setTimeout(() => {
      currentIndex = index;
      isScrolling = false;
      lastScrollTime = Date.now();
    }, 700);
  }

  container.addEventListener("wheel", function (event) {
    const now = Date.now();
    // 冷却时间内忽略事件,同时过滤触控板的微小滚动事件
    if (now - lastScrollTime < SCROLL_COOLDOWN || Math.abs(event.deltaY) < MIN_DELTA_Y) {
      return;
    }

    // 只处理有效的滚动方向
    if (event.deltaY > 0) {
      scrollToSection(currentIndex + 1);
    } else {
      scrollToSection(currentIndex - 1);
    }
  });
});

方案二:区分鼠标和触控板事件,针对性处理

我们可以通过event.deltaModeevent.deltaY的特征来区分鼠标和触控板:鼠标滚轮的deltaY通常是±100(默认步长),而触控板的deltaY会是连续的小数值,且deltaMode多为0(像素模式)。

wheel事件中加入判断:

document.addEventListener("DOMContentLoaded", function () {
  const container = document.querySelector(".container");
  const sections = document.querySelectorAll(".image-section");
  let currentIndex = 0;
  let isScrolling = false;
  let touchpadAccumulatedDelta = 0;
  const TOUCHPAD_THRESHOLD = 150; // 触控板累计滚动阈值

  function scrollToSection(index) {
    if (index < 0 || index >= sections.length || isScrolling) return;
    isScrolling = true;
    sections[index].scrollIntoView({ behavior: "smooth" });
    setTimeout(() => {
      currentIndex = index;
      isScrolling = false;
      touchpadAccumulatedDelta = 0; // 重置触控板累计值
    }, 700);
  }

  container.addEventListener("wheel", function (event) {
    if (isScrolling) return;

    // 判断是否为鼠标滚轮事件(deltaY为±100左右的整数)
    const isMouseWheel = Math.abs(event.deltaY) >= 90 && Math.abs(event.deltaY) <= 110;
    
    if (isMouseWheel) {
      // 鼠标滚轮:直接响应单步滚动
      scrollToSection(event.deltaY > 0 ? currentIndex + 1 : currentIndex - 1);
    } else {
      // 触控板:累计滚动值,超过阈值才触发滚动
      touchpadAccumulatedDelta += event.deltaY;
      if (Math.abs(touchpadAccumulatedDelta) >= TOUCHPAD_THRESHOLD) {
        const direction = touchpadAccumulatedDelta > 0 ? 1 : -1;
        scrollToSection(currentIndex + direction);
      }
    }
  });
});

方案三:结合原生scroll-snap,减少自定义逻辑冲突

你的容器已经设置了scroll-snap-type: y mandatory,可以利用原生滚动吸附特性,只在用户滚动结束后修正位置,减少自定义逻辑的侵入性:

  1. 先移除容器的scroll-behavior: smooth(避免和自定义滚动冲突)
  2. 修改事件监听逻辑:
document.addEventListener("DOMContentLoaded", function () {
  const container = document.querySelector(".container");
  const sections = document.querySelectorAll(".image-section");
  let currentIndex = 0;
  let scrollEndTimeout;

  // 监听滚动停止事件,自动修正到最近的section
  container.addEventListener("scroll", function() {
    clearTimeout(scrollEndTimeout);
    scrollEndTimeout = setTimeout(() => {
      // 计算当前可见的section索引
      const visibleIndex = Array.from(sections).findIndex(section => {
        const rect = section.getBoundingClientRect();
        return rect.top <= window.innerHeight / 2 && rect.bottom >= window.innerHeight / 2;
      });
      if (visibleIndex !== -1 && visibleIndex !== currentIndex) {
        currentIndex = visibleIndex;
        sections[currentIndex].scrollIntoView({ behavior: "smooth" });
      }
    }, 200);
  });

  // 保留wheel事件用于快速跳转,同时阻止原生连续滚动
  container.addEventListener("wheel", function (event) {
    if (event.deltaY > 0 && currentIndex < sections.length - 1) {
      scrollToSection(currentIndex + 1);
    } else if (event.deltaY < 0 && currentIndex > 0) {
      scrollToSection(currentIndex - 1);
    }
    event.preventDefault();
  }, { passive: false }); // 必须设置passive:false才能调用preventDefault

  function scrollToSection(index) {
    sections[index].scrollIntoView({ behavior: "smooth" });
    currentIndex = index;
  }
});

额外CSS优化建议

给容器添加touch-action: pan-y,明确告诉浏览器允许垂直触控滚动,避免手势冲突:

.container {
  /* 原有样式 */
  touch-action: pan-y;
}

你之前尝试的event.preventDefault()之所以会禁用平滑滚动,是因为直接阻止了所有原生滚动行为。上面的方案要么过滤无效事件,要么在滚动结束后修正位置,既保留了平滑滚动体验,又能稳定控制滚动到指定section。

备注:内容来源于stack exchange,提问作者RajDave

火山引擎 最新活动