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

点击按钮实现Flexbox项左右滑动过渡的组件开发咨询

问题描述

我想开发一个类似NBA官网顶部的日程展示组件,或是NFL官网上可切换不同周的导航组件——点击侧边按钮时,flex容器内的项能向左或向右滑动。

我已经用日期做了示例,样式部分符合需求,但不知道怎么实现点击按钮调整flexbox项滑动的功能,也没找到同类组件的参考方案。

当前代码如下:

App.tsx

import "./App.css";

function getDaysInMonth(month: number, year: number) {
  const date = new Date(year, month - 1, 1);
  const days = [];
  while (date.getMonth() === month - 1) {
    days.push(new Date(date));
    date.setDate(date.getDate() + 1);
  }
  return days;
}

function App() {
  const dates = getDaysInMonth(12, 2023);
  console.log(dates[0]);

  return (
    <div className="p-8">
      <div className="w-full border text-center bg-zinc-200 mb-2">
        <h1>App</h1>
      </div>
      <div className="flex gap-2">
        <button className="border w-24 hover:bg-zinc-200">L</button>
        <div className="mx-8 border max-w-screen h-12 flex gap-4 items-center justify-start overflow-x-auto">
          {dates.map((date) => {
            date.setHours(0, 0, 0, 0);
            const month = date.toLocaleString("default", { month: "short" });
            const day = date.getDate();
            console.log(month + day);
            return (
              <div
                key={day}
                className="border text-sm flex p-2"
                onClick={() => console.log("clicked " + month + day)}
              >
                {month} <span className="ml-1">{day}</span>
              </div>
            );
          })}
        </div>
        <button className="border w-24 hover:bg-zinc-200">R</button>
      </div>
    </div>
  );
}

export default App;

App.css

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

解决方案

要实现按钮控制flex容器的横向滑动,核心是利用DOM元素的scrollBy方法,结合React的useRef获取容器引用,具体实现步骤如下:

  1. 引入useRef:获取flex容器的DOM节点,用于直接操作滚动行为。
  2. 添加滚动逻辑:给左右按钮绑定点击事件,调用scrollBy方法控制滚动距离(正数向右滚动,负数向左滚动)。
  3. 可选优化:如果需要精准滚动(比如每次滚动固定数量的日期项),可以获取单个日期项的宽度,计算滚动步长。

修改后的完整代码:

import "./App.css";
import { useRef } from "react";

function getDaysInMonth(month: number, year: number) {
  const date = new Date(year, month - 1, 1);
  const days = [];
  while (date.getMonth() === month - 1) {
    days.push(new Date(date));
    date.setDate(date.getDate() + 1);
  }
  return days;
}

function App() {
  const dates = getDaysInMonth(12, 2023);
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  // 每次滚动的步长,可根据需求调整
  const scrollStep = 200;

  const handleScrollLeft = () => {
    scrollContainerRef.current?.scrollBy({
      left: -scrollStep,
      behavior: "smooth" // 开启平滑滚动,可选
    });
  };

  const handleScrollRight = () => {
    scrollContainerRef.current?.scrollBy({
      left: scrollStep,
      behavior: "smooth"
    });
  };

  return (
    <div className="p-8">
      <div className="w-full border text-center bg-zinc-200 mb-2">
        <h1>App</h1>
      </div>
      <div className="flex gap-2">
        <button 
          className="border w-24 hover:bg-zinc-200"
          onClick={handleScrollLeft}
        >
          L
        </button>
        <div 
          ref={scrollContainerRef}
          className="mx-8 border max-w-screen h-12 flex gap-4 items-center justify-start overflow-x-auto"
        >
          {dates.map((date) => {
            date.setHours(0, 0, 0, 0);
            const month = date.toLocaleString("default", { month: "short" });
            const day = date.getDate();
            return (
              <div
                key={date.getTime()} // 用getTime()避免日期重复的key冲突
                className="border text-sm flex p-2 min-w-[60px] justify-center" // 设置最小宽度保证布局一致
                onClick={() => console.log("clicked " + month + day)}
              >
                {month} <span className="ml-1">{day}</span>
              </div>
            );
          })}
        </div>
        <button 
          className="border w-24 hover:bg-zinc-200"
          onClick={handleScrollRight}
        >
          R
        </button>
      </div>
    </div>
  );
}

export default App;

关键说明:

  • useRef的使用:scrollContainerRef绑定到flex容器,确保能直接访问其DOM属性和方法。
  • scrollBy方法:scrollBy({ left: 数值, behavior: "smooth" })控制滚动,behavior: "smooth"实现平滑滚动效果,也可以设置为"auto"立即滚动。
  • key优化:将原来的key={day}改为key={date.getTime()},避免当月有重复日期(虽然不会出现,但更严谨)。
  • 日期项宽度:给日期项添加min-w-[60px],保证每个项宽度一致,滚动步长的计算更精准。

如果想要实现精准滚动N个日期项,可以在组件挂载后获取单个日期项的宽度,动态计算滚动步长:

import { useRef, useEffect, useState } from "react";

// ...其他代码

function App() {
  const dates = getDaysInMonth(12, 2023);
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const [itemWidth, setItemWidth] = useState<number>(0);
  const itemsToScroll = 3; // 每次滚动3个项

  useEffect(() => {
    // 获取第一个日期项的宽度
    const firstItem = scrollContainerRef.current?.querySelector("div");
    if (firstItem) {
      setItemWidth(firstItem.offsetWidth + 16); // 加上gap的4*4=16px
    }
  }, []);

  const handleScrollLeft = () => {
    scrollContainerRef.current?.scrollBy({
      left: -itemWidth * itemsToScroll,
      behavior: "smooth"
    });
  };

  const handleScrollRight = () => {
    scrollContainerRef.current?.scrollBy({
      left: itemWidth * itemsToScroll,
      behavior: "smooth"
    });
  };

  // ...返回JSX部分不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 13:56:01