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

Nextjs/React中如何实现单图横向拖拽滚动自定义水平滚动条

Next.js+React+TS+Tailwind 实现可横向拖拽浏览的Hero单图组件

核心实现思路

  • 外层做固定视口大小的滚动容器,隐藏原生滚动条,开启横向溢出滚动
  • 内层放置超宽Hero图,宽度按图片原始比例设置,保证高度铺满视口不变形
  • 监听鼠标、触摸事件,记录拖拽起始位置,实时同步容器横向滚动偏移
  • 增加拖拽光标切换、滚动惯性、默认交互禁止的细节处理,保证拖拽跟手度
  • 拖拽状态用ref存储而非state,避免频繁重渲染导致拖拽卡顿

完整组件代码

'use client' // Next.js App Router 模式需添加,标记为客户端组件;Pages Router可删除
import Image from 'next/image'
import { useRef, useEffect, useState } from 'react'

export default function DraggableHero() {
  const scrollContainerRef = useRef<HTMLDivElement>(null)
  // 拖拽状态用ref存储,避免频繁触发重渲染
  const isDragging = useRef(false)
  const startX = useRef(0)
  const startScrollLeft = useRef(0)
  const lastX = useRef(0)
  const velocity = useRef(0)
  // 仅用于切换光标样式的状态
  const [isDraggingActive, setIsDraggingActive] = useState(false)

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

    // 拖拽开始:初始化状态
    const handleDragStart = (e: MouseEvent | TouchEvent) => {
      isDragging.current = true
      setIsDraggingActive(true)
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      startX.current = clientX
      lastX.current = clientX
      startScrollLeft.current = container.scrollLeft
      velocity.current = 0
      container.style.cursor = 'grabbing'
    }

    // 拖拽移动:同步滚动位置
    const handleDragMove = (e: MouseEvent | TouchEvent) => {
      if (!isDragging.current) return
      e.preventDefault()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      const deltaX = clientX - startX.current
      container.scrollLeft = startScrollLeft.current - deltaX
      // 记录移动速度用于惯性计算
      velocity.current = clientX - lastX.current
      lastX.current = clientX
    }

    // 拖拽结束:执行惯性动画
    const handleDragEnd = () => {
      if (!isDragging.current) return
      isDragging.current = false
      setIsDraggingActive(false)
      container.style.cursor = 'grab'

      // 惯性衰减动画
      const runInertia = () => {
        if (Math.abs(velocity.current) < 0.5) return
        velocity.current *= 0.95 // 摩擦系数,越小停顿越快
        container.scrollLeft -= velocity.current
        requestAnimationFrame(runInertia)
      }
      requestAnimationFrame(runInertia)
    }

    // 绑定事件,事件挂载到window避免拖拽出容器区域失效
    container.addEventListener('mousedown', handleDragStart)
    container.addEventListener('touchstart', handleDragStart, { passive: true })
    window.addEventListener('mousemove', handleDragMove)
    window.addEventListener('touchmove', handleDragMove, { passive: false })
    window.addEventListener('mouseup', handleDragEnd)
    window.addEventListener('touchend', handleDragEnd)

    return () => {
      container.removeEventListener('mousedown', handleDragStart)
      container.removeEventListener('touchstart', handleDragStart)
      window.removeEventListener('mousemove', handleDragMove)
      window.removeEventListener('touchmove', handleDragMove)
      window.removeEventListener('mouseup', handleDragEnd)
      window.removeEventListener('touchend', handleDragEnd)
    }
  }, [])

  return (
    <section className="w-full h-screen relative overflow-hidden">
      <div
        ref={scrollContainerRef}
        className={`w-full h-full overflow-x-scroll overflow-y-hidden scrollbar-hide select-none cursor-grab ${isDraggingActive ? 'cursor-grabbing' : ''}`}
        draggable={false}
      >
        {/* 内层图片容器,宽度可根据图片实际比例调整 */}
        <div className="relative h-full w-[250vw]">
          <Image
            src="/your-hero-wide-image.jpg" // 替换为项目内实际超宽Hero图路径
            alt="Hero banner"
            fill
            draggable={false}
            className="object-cover object-left pointer-events-none"
            priority
          />
        </div>
      </div>
      {/* 可选:添加拖拽提示文案 */}
      <div className="absolute bottom-8 left-1/2 -translate-x-1/2 text-white/80 text-sm pointer-events-none">
        拖拽浏览完整图片
      </div>
    </section>
  )
}

Tailwind 配置补充

在项目全局CSS文件中添加隐藏滚动条的自定义工具类,无需引入额外插件:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;
  }
  .scrollbar-hide::-webkit-scrollbar {
    display: none;
  }
}

可调优化项

  • 内层图片容器不要硬编码固定宽度,可根据图片原始宽高比动态计算:比如图片原始分辨率为3000*1080,容器高度为100vh时,宽度设置为calc(100vh * (3000 / 1080)),可保证图片高度刚好铺满视口,无拉伸变形
  • 惯性动画的摩擦系数可按需调整,数值越大惯性滑动距离越长,数值越小停顿越快
  • 若需要支持鼠标滚轮横向滚动,可额外监听容器wheel事件,将纵向滚动的delta值转换为横向滚动偏移
  • 移动端如果需要更贴近原生的滚动手感,可移除自定义惯性逻辑,直接使用浏览器原生滚动的惯性效果,只保留拖拽同步的核心逻辑即可

内容的提问来源于stack exchange,提问作者Enadot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 17:36:17