Skip to content

🖱️ 鼠标滚轮实现平滑水平滚动 ​

在现代网页开发中,水平滚动的交互设计越来越常见,特别是在图片画廊、时间轴等场景中。本文将详细介绍如何将鼠标的垂直滚动转换为平滑的水平滚动效果。

🎯 实现目标 ​

  • 将鼠标滚轮的垂直滚动转换为水平方向
  • 实现平滑的滚动动画效果
  • 确保良好的性能和用户体验
  • 支持主流浏览器

💡 实现方案 ​

1️⃣ 方案一:物理动画效果(requestAnimationFrame) ​

这种方案通过模拟物理运动中的惯性和摩擦力,实现更自然的滚动效果。

html
<!DOCTYPE html>
<html>
<head>
  <style>
    .container {
      width: 500px;
      height: 300px;
      overflow-x: scroll;
      white-space: nowrap;
    }
    .content {
      width: 2000px;
      height: 100%;
      background: linear-gradient(to right, #ff0000, #00ff00);
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="content"></div>
  </div>
  <script>
    const container = document.querySelector('.container');
    let velocity = 0;
    let animationFrame = null;

    function handleWheel(e) {
      e.preventDefault();
      const speedFactor = 0.3;
      velocity += e.deltaY * speedFactor;

      if (!animationFrame) {
        const animate = () => {
          if (Math.abs(velocity) < 0.1) {
            velocity = 0;
            animationFrame = null;
            return;
          }
          container.scrollLeft += velocity;
          velocity *= 0.95; // 添加摩擦力
          animationFrame = requestAnimationFrame(animate);
        };
        animationFrame = requestAnimationFrame(animate);
      }
    }

    container.addEventListener('wheel', handleWheel, { passive: false });
  </script>
</body>
</html>

2️⃣ 方案二:CSS Scroll Behavior ​

利用现代浏览器原生的平滑滚动能力,实现更简单且高性能的效果。

html
<!DOCTYPE html>
<html>
<head>
  <style>
    .container {
      width: 500px;
      height: 300px;
      overflow-x: scroll;
      white-space: nowrap;
      scroll-behavior: smooth; /* 关键属性 */
    }
    .content {
      width: 2000px;
      height: 100%;
      background: linear-gradient(to right, #ff0000, #00ff00);
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="content"></div>
  </div>
  <script>
    const container = document.querySelector('.container');
    let scrollTimeout = null;
    let accumulatedDelta = 0;

    function handleWheel(e) {
      e.preventDefault();
      accumulatedDelta += e.deltaY;
      
      if (scrollTimeout) clearTimeout(scrollTimeout);
      
      scrollTimeout = setTimeout(() => {
        container.scrollTo({
          left: container.scrollLeft + accumulatedDelta,
          behavior: 'smooth'
        });
        accumulatedDelta = 0;
      }, 50);
    }

    container.addEventListener('wheel', handleWheel, { passive: false });
  </script>
</body>
</html>

⚛️ React 组件实现 ​

使用 React Hooks 的最佳实践 ​

jsx
import React, { useEffect, useRef, useCallback } from 'react';

const SmoothHorizontalScroll = () => {
  const containerRef = useRef(null);
  const velocityRef = useRef(0);
  const animationFrameRef = useRef(null);

  const handleWheel = useCallback((e) => {
    e.preventDefault();
    const speedFactor = 0.3;
    velocityRef.current += e.deltaY * speedFactor;

    if (!animationFrameRef.current) {
      const animate = () => {
        if (Math.abs(velocityRef.current) < 0.1) {
          velocityRef.current = 0;
          animationFrameRef.current = null;
          return;
        }
        containerRef.current.scrollLeft += velocityRef.current;
        velocityRef.current *= 0.95;
        animationFrameRef.current = requestAnimationFrame(animate);
      };
      animationFrameRef.current = requestAnimationFrame(animate);
    }
  }, []);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    container.addEventListener('wheel', handleWheel, { passive: false });

    return () => {
      container.removeEventListener('wheel', handleWheel);
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current);
      }
    };
  }, [handleWheel]);

  return (
    <div 
      ref={containerRef}
      className="smooth-scroll-container"
      style={{
        width: '500px',
        height: '300px',
        overflowX: 'scroll',
        whiteSpace: 'nowrap',
        scrollBehavior: 'smooth'
      }}
    >
      <div className="smooth-scroll-content" />
    </div>
  );
};

export default SmoothHorizontalScroll;

🎨 性能优化建议 ​

  1. 防抖处理
javascript
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};
  1. 使用 CSS 硬件加速
css
.smooth-scroll-container {
  transform: translateZ(0);
  will-change: transform;
}

⚠️ 注意事项 ​

  1. 触摸设备兼容性
  • 建议添加触摸事件支持
  • 考虑移动端的滑动手势
  1. 性能监控
  • 监控 FPS
  • 避免频繁的 DOM 操作
  1. 可访问性
  • 确保键盘导航可用
  • 添加适当的 ARIA 属性

🌐 浏览器兼容性 ​

特性ChromeFirefoxSafariEdge
requestAnimationFrame24+23+6.1+12+
scroll-behavior61+36+15.4+79+

📱 移动端适配示例 ​

javascript
const isTouchDevice = 'ontouchstart' in window;

if (isTouchDevice) {
  container.addEventListener('touchmove', (e) => {
    // 处理触摸滑动
    const touch = e.touches[0];
    // 实现触摸滑动逻辑
  });
}

🎉 总结 ​

选择合适的实现方案时需要考虑:

  • 🎯 项目需求和交互设计
  • 💻 目标用户的设备和浏览器
  • ⚡ 性能要求
  • 🛠️ 维护成本

两种方案各有特点:

  • 物理动画方案:更好的用户体验,更精确的控制
  • CSS Scroll Behavior:更简单的实现,更好的性能