React PC端悬浮锚点吸顶导航

需求描述

  • 实现PC端吸顶导航

实现分析

  • 先采用absolute绝对定位
  • 滚动超过锚点导航时,修改该导航定位为fixed

效果预览

实现方法

/** 锚点导航*/
import React from "react";
import styles from "./index.less";
import classnames from "classnames";
import { ActiveType } from "@17zwd/fe-egg-factory-page/detail/type";

interface PageState {
  // 目录激活项
  activeItem?: string;
  // 是否吸顶
  isSticky?: boolean;
}

class AnchorNav extends React.Component<any, PageState> {
  private scrollTimer;
  constructor(props) {
    super(props);
    this.state = {
      activeItem: ActiveType.baseInfo,
      isSticky: false,
    };
  }

  componentDidMount(): void {
    // 绑定页面滚动事件
    window.addEventListener("scroll", this.onPageScroll);
  }

  componentWillUnmount(): void {
    // 解绑页面滚动事件
    window.removeEventListener("scroll", this.onPageScroll);
  }

  compare = (prop) => {
    return function (a, b) {
      const value1 = a[prop];
      const value2 = b[prop];
      return value1 - value2;
    };
  };

  // 页面滚动事件
  onPageScroll = (): void => {
    if (this.scrollTimer) {
      clearTimeout(this.scrollTimer);
    }
    this.scrollTimer = setTimeout(() => {
      // 吸顶
      const firstItemTop = document
        .getElementById(ActiveType.baseInfo)
        ?.getBoundingClientRect().top;
      if (firstItemTop && firstItemTop < 0) {
        this.setState({
          isSticky: true,
        });
      } else {
        this.setState({
          isSticky: false,
        });
      }
      // 动态高亮
      const viewTops: Array<any> = [];
      for (const i in ActiveType) {
        const id = ActiveType[i];
        const ele = document.getElementById(id);
        if (!ele) return;
        const top = ele?.getBoundingClientRect().top;
        if (top < 0) continue;
        viewTops.push({
          id,
          top,
        });
      }
      viewTops.sort(this.compare("top"));
      this.setState({
        activeItem: viewTops[0]?.id,
      });
    }, 90);
  };

  //跳转到的锚点
  onScrollToAnchor = (activeItem: string): void => {
    if (activeItem) {
      // 找到锚点
      const anchorElement = document.getElementById(activeItem);
      // 如果对应id的锚点存在,就跳转到锚点
      if (anchorElement) {
        anchorElement.scrollIntoView({ block: "start", behavior: "smooth" });
        this.setState({
          activeItem,
        });
      }
    }
  };

  getItemName = (en: string): string => {
    let res = "";
    switch (en) {
      case ActiveType.baseInfo:
        res = "基础信息";
        break;
      case ActiveType.certify:
        res = "资质认证";
        break;
      case ActiveType.overview:
        res = "综合概览";
        break;
      case ActiveType.remark:
        res = "工厂简介";
        break;
      case ActiveType.pics:
        res = "工厂实拍";
        break;
      case ActiveType.product:
        res = "产品案例";
        break;
      default:
        break;
    }
    return res;
  };

  renderItem = (): JSX.Element => {
    const { activeItem } = this.state;
    const items: Array<any> = [];
    for (const i in ActiveType) {
      const element = ActiveType[i];
      if (element) {
        items.push(element);
      }
    }
    return (
      <React.Fragment>
        {items.map((item, index) => {
          return (
            <li
              key={index}
              onClick={() => this.onScrollToAnchor(item)}
              className={activeItem === item ? styles.active : ""}
            >
              <span
                className={classnames(styles.anchorItem, styles.anchorNoBorder)}
              >
                {this.getItemName(item)}
              </span>
            </li>
          );
        })}
      </React.Fragment>
    );
  };

  render(): JSX.Element {
    const { isSticky } = this.state;
    return (
      <React.Fragment>
        <div
          className={classnames(
            styles.mainAnchor,
            isSticky ? styles.positionSticky : styles.positionAbsolute
          )}
        >
          <ul className={styles.anchorList}>{this.renderItem()}</ul>
        </div>
      </React.Fragment>
    );
  }
}

export default AnchorNav;

@import "@/less/mixin.less";

// 导航
.positionSticky {
  position: fixed;
  margin-left: -124px;
  z-index: 1;
}
.positionAbsolute {
  position: absolute;
  left: -124px;
}
.mainAnchor {
  top: 0;
   124px;
  .anchorList {
    margin-bottom: 0;
    padding: 0 24px;
     112px;
    box-sizing: border-box;
    background-color: @background-secondary-color;
    > li {
      position: relative;
      color: @text-thrid-color;
      transition: all 0.2s linear;
      &:hover {
        color: @main-primary-color;
      }
    }
    .anchorItem {
      display: block;
      padding: 16px 0;
      height: 53px;
      font-size: 16px;
      line-height: 21px;
      box-sizing: border-box;
      border-top: solid 1px @border-primary-color;
      cursor: pointer;
    }
    .anchorNoBorder {
      border-top: none;
    }
    .active {
      color: @main-primary-color;
      &::before {
        position: absolute;
        content: "";
        left: -14px;
        top: 24px;
         6px;
        height: 6px;
        border-radius: 50%;
        background-color: @main-primary-color;
      }
    }
  }
  .navBox {
    visibility: hidden;
  }
}

原文地址:https://www.cnblogs.com/KevinTseng/p/14597805.html