React 滚动监听 和 tab 切换

Tabs组件

//Tabs.tsx
import React from "react";
import classNames from "classnames";
import "./index.scss";

interface IProps {
  readonly tabs: Array<Itab>;
  readonly activeTab: string;
  readonly tabActiveHandle: (tabActive: string) => void;
}

interface Itab {
  readonly key: string;
  readonly label: string;
}

export const Tabs = (props: IProps) => {
  const tabsChange = (key: string) => {
    props.tabActiveHandle(key);
  };

  return (
    <div className='setting-tab'>
      {props.tabs.map((item, i) => (
        <div
          key={i}
          onClick={() => tabsChange(item.key)}
          className={classNames(
            "tabs",
            props.activeTab === item.key && "tabs-active"
          )}
        >
          {item.label}
        </div>
      ))}
    </div>
  );
};

//Tabs css
.setting-tab {
  height: 100%;
  width: 136px;
  display: flex;
  flex-direction: column;
  .tabs {
    cursor: pointer;
    height: 70px;
    line-height: 70px;
    font-size: 18px;
    text-align: center;
    position: relative;
    top: 0;
    right: -1px;
    &.tabs-active {
      border-right: 1px solid #ffb900;
      background: linear-gradient(-90deg, #e4bf7633 -8.46%, #e4bf7600 127.57%);
    }
  }
}

父组件

// FunctionSetting.tsx
import { useEffect, useState } from "react";
import { Tabs } from "./Tabs";
import { debounce } from "lodash";
import React from "react";

const CONSTANTS = {
  CONTENT_ID: "content",
  TABS: [
    {
      key: "tab1",
      label: "tab1",
    },
    {
      key: "tab2",
      label: "tab2",
    },
  ],
};

export default function FunctionSetting() {
  const [activeTab, setActiveTab] = useState<string>(CONSTANTS.TABS[1].key);

  // 默认执行一遍
  useEffect(() => {
    tabActiveHandle(activeTab);
  }, []);

  // 滚动条监听
  useEffect(() => {
    const container = document.getElementById(CONSTANTS.CONTENT_ID);
    if (container) {
      container.addEventListener("scroll", handleScroll);
    }
    return () => {
      if (container) {
        container.removeEventListener("scroll", handleScroll);
      }
    };
  }, []);

  // 滚动函数
  const handleScroll = debounce(() => {
    const containerId = CONSTANTS.CONTENT_ID;
    const reversedTabs = [...Object.values(CONSTANTS.TABS)].reverse();
    // 使用循环检查每个 tab 是否在视口中
    for (const tab of reversedTabs) {
      if (isElementInViewport(containerId, tab.key)) {
        setActiveTab(tab.key);
        break; // 找到第一个符合条件的 tab,跳出循环
      }
    }
  }, 100);

  // 判断元素是否在容器内
  const isElementInViewport = (boxId: string, elId: string) => {
    const container = document.getElementById(boxId);
    const el = document.getElementById(elId);
    if (container && el) {
      const containerRect = container.getBoundingClientRect() || {
        top: 0,
        bottom: 0,
      };
      const rect = el.getBoundingClientRect();
      return (
        rect.top < containerRect.bottom && rect.bottom >= containerRect.top
      );
    }
    return false;
  };

  // 执行滚动到具体tab
  const tabActiveHandle = (tabActive: string) => {
    setActiveTab(tabActive);
    const targetElement = document.getElementById(tabActive);
    if (targetElement) {
      targetElement.scrollIntoView({ behavior: "smooth" });
    }
  };

  // 组件1
  const renderComponent1 = () => {
    const data = [];
    for (let i = 1; i <= 10; i++) {
      data.push(`数据项 ${i}`);
    }
    return (
      <div>
        <h3>组件1</h3>
        <ul>
          {data.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      </div>
    );
  };

  // 组件2
  const renderComponent2 = () => {
    return (
      <div>
        <h3>组件2</h3>
        <ul>
          <li>123</li>
        </ul>
      </div>
    );
  };

  return (
    <div
      style={{
        display: "flex",
        height: "200px",
        backgroundColor: "ghostwhite",
      }}
    >
      <Tabs
        tabs={CONSTANTS.TABS}
        activeTab={activeTab}
        tabActiveHandle={tabActiveHandle}
      />
      <div
        id={CONSTANTS.CONTENT_ID}
        style={{
          flex: 1,
          overflowY: "auto",
          height: "100%",
          paddingLeft: "33px",
        }}
      >
        <div id={CONSTANTS.TABS[0].key}>{renderComponent1()}</div>
        <div id={CONSTANTS.TABS[1].key}>{renderComponent2()}</div>
      </div>
    </div>
  );
}

posted @ 2025-06-10 18:07  苏沐~  阅读(34)  评论(0)    收藏  举报