Ant-Design源码分析——Anchor

2021SC@SDUSC

anchor锚点的位置用于快速定位链接的指定位置

先来看下接口内定义的参数

export interface AnchorProps {
  // 前缀信息
  prefixCls?: string;
  // 类名
  className?: string;
  // 样式
  style?: React.CSSProperties;
  // 子类对象
  children?: React.ReactNode;
  // 距离窗口顶端的偏移量
  offsetTop?: number;
  // 区域边界
  bounds?: number;
  // 是否固定
  affix?: boolean;
  // 固定时是否显示小圆点
  showInkInFixed?: boolean;
  // 获取容器
  getContainer?: () => AnchorContainer;
  /** Return customize highlight anchor */
  getCurrentAnchor?: () => string;
  // 点击事件(跳转)
  onClick?: (
    e: React.MouseEvent<HTMLElement>,
    link: { title: React.ReactNode; href: string },
  ) => void;
  /** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
  // 锚点滚动偏移量,默认与offsetTop相同
  targetOffset?: number;
  /** Listening event when scrolling change active link */
  // 用于监听锚点的变化
  onChange?: (currentActiveLink: string) => void;
}

类定义

export default class Anchor extends React.Component<AnchorProps, AnchorState, ConfigConsumerProps> {
  // 每个链接是由AnchorLink保存
  static Link: typeof AnchorLink;

  /// 默认固定不显示圆点
  static defaultProps = {
    affix: true,
    showInkInFixed: false,
  };

  static contextType = ConfigContext;

  state = {
    activeLink: null,
  };

  content: ConfigConsumerProps;

  private wrapperRef = React.createRef<HTMLDivElement>();
  
  // 圆点样式
  private inkNode: HTMLSpanElement;

  // scroll scope's container
  private scrollContainer: HTMLElement | Window;

  // anchor的链接
  private links: string[] = [];
  
  // 鼠标滚轮的事件
  private scrollEvent: any;

  private animating: boolean;

  private prefixCls?: string;

  // 添加link
  registerLink = (link: string) => {
    if (!this.links.includes(link)) {
      this.links.push(link);
    }
  };

  // 删除link
  unregisterLink = (link: string) => {
    const index = this.links.indexOf(link);
    if (index !== -1) {
      this.links.splice(index, 1);
    }
  };

  // 返回本容器或目标容器,都没有返回默认容器
  getContainer = () => {
    const { getTargetContainer } = this.context;
    const { getContainer } = this.props;

    const getFunc = getContainer || getTargetContainer || getDefaultContainer;

    return getFunc();
  };

  componentDidMount() {
    this.scrollContainer = this.getContainer();
    this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
    this.handleScroll();
  }

  componentDidUpdate() {
    if (this.scrollEvent) {
      const currentContainer = this.getContainer();
      if (this.scrollContainer !== currentContainer) {
        this.scrollContainer = currentContainer;
        this.scrollEvent.remove();
        this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
        this.handleScroll();
      }
    }
    this.updateInk();
  }

  componentWillUnmount() {
    if (this.scrollEvent) {
      this.scrollEvent.remove();
    }
  }

  getCurrentAnchor(offsetTop = 0, bounds = 5): string {
    const linkSections: Array<Section> = [];
    const container = this.getContainer();
    this.links.forEach(link => {
      const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
      if (!sharpLinkMatch) {
        return;
      }
      const target = document.getElementById(sharpLinkMatch[1]);
      if (target) {
        const top = getOffsetTop(target, container);
        if (top < offsetTop + bounds) {
          linkSections.push({
            link,
            top,
          });
        }
      }
    });

    if (linkSections.length) {
      const maxSection = linkSections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
      return maxSection.link;
    }
    return '';
  }

  // 滚动到链接指定的位置
  handleScrollTo = (link: string) => {
    const { offsetTop, targetOffset } = this.props;

    this.setCurrentActiveLink(link);
    const container = this.getContainer();
    const scrollTop = getScroll(container, true);
    const sharpLinkMatch = sharpMatcherRegx.exec(link);
    if (!sharpLinkMatch) {
      return;
    }
    // 获取对应目标元素(link对应的)
    const targetElement = document.getElementById(sharpLinkMatch[1]);
    if (!targetElement) {
      return;
    }

    // 计算对应元素所在位置
    const eleOffsetTop = getOffsetTop(targetElement, container);
    // 计算要移动到的位置
    let y = scrollTop + eleOffsetTop;
    // 查看是否匹配
    y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
    this.animating = true;
    // 移动
    scrollTo(y, {
      callback: () => {
        this.animating = false;
      },
      getContainer: this.getContainer,
    });
  };

  saveInkNode = (node: HTMLSpanElement) => {
    this.inkNode = node;
  };

  // 设置当前显示的链接位置
  setCurrentActiveLink = (link: string) => {
    const { activeLink } = this.state;
    const { onChange, getCurrentAnchor } = this.props;
    if (activeLink === link) {
      return;
    }
    // https://github.com/ant-design/ant-design/issues/30584
    this.setState({
      activeLink: typeof getCurrentAnchor === 'function' ? getCurrentAnchor() : link,
    });
    onChange?.(link);
  };

  // 移动到指定位置
  handleScroll = () => {
    if (this.animating) {
      return;
    }
    const { offsetTop, bounds, targetOffset } = this.props;
    const currentActiveLink = this.getCurrentAnchor(
      targetOffset !== undefined ? targetOffset : offsetTop || 0,
      bounds,
    );
    this.setCurrentActiveLink(currentActiveLink);
  };

  // 保存当前存档点
  updateInk = () => {
    const { prefixCls, wrapperRef } = this;
    const anchorNode = wrapperRef.current;
    const linkNode = anchorNode?.getElementsByClassName(`${prefixCls}-link-title-active`)[0];

    if (linkNode) {
      this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
    }
  };

  render = () => {
    const { getPrefixCls, direction } = this.context;
    // 保存组件需要用到的元素
    const {
      prefixCls: customizePrefixCls,
      className = '',
      style,
      offsetTop,
      affix,
      showInkInFixed,
      children,
    } = this.props;
    const { activeLink } = this.state;

    const prefixCls = getPrefixCls('anchor', customizePrefixCls);

    // To support old version react.
    // Have to add prefixCls on the instance.
    // https://github.com/facebook/react/issues/12397
    this.prefixCls = prefixCls;

    const inkClass = classNames(`${prefixCls}-ink-ball`, {
      visible: activeLink,
    });

    const wrapperClass = classNames(
      `${prefixCls}-wrapper`,
      {
        [`${prefixCls}-rtl`]: direction === 'rtl',
      },
      className,
    );

    const anchorClass = classNames(prefixCls, {
      fixed: !affix && !showInkInFixed,
    });

    const wrapperStyle = {
      maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
      ...style,
    };

    // 定义anchor保存到内容(属性信息)
    const anchorContent = (
      <div ref={this.wrapperRef} className={wrapperClass} style={wrapperStyle}>
        <div className={anchorClass}>
          <div className={`${prefixCls}-ink`}>
            <span className={inkClass} ref={this.saveInkNode} />
          </div>
          {children}
        </div>
      </div>
    );

    // 返回组件
    return (
      
      <AnchorContext.Provider
        value={{
          registerLink: this.registerLink,
          unregisterLink: this.unregisterLink,
          activeLink: this.state.activeLink,
          scrollTo: this.handleScrollTo,
          onClick: this.props.onClick,
        }}
      >
        {!affix ? (
          anchorContent
        ) : (
          <Affix offsetTop={offsetTop} target={this.getContainer}>
            {anchorContent}
          </Affix>
        )}
      </AnchorContext.Provider>
    );
  };
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
<p><span xss=removed>Ant Design 是阿里开的一套企业级的 UI 设计语言和 React 实现,使用 TypeScript 构建,提供完整的类型定义文件,自带提炼自企业级中后台产品的交互语言和视觉风格、开箱即用的高质量 React 组件与全链路开发和设计工具体系。</span></p><p> </p><p>特性:</p><p>提炼自企业级中后台产品的交互语言和视觉风格。</p><p>开箱即用的高质量 React 组件。</p><p>使用 TypeScript 开发,提供完整的类型定义文件。</p><p>全链路开发和设计工具体系。</p><p>数十个国际化语言支持。</p><p>深入每个细节的主题定制能力。</p><p> </p><p>兼容环境:</p><p>现代浏览器和 IE11(需要 polyfills)。</p><p>支持服务端渲染。</p><p>Electron</p><p> </p><p>Ant Design 更新日志:</p><p>v4.16.0</p><p>重构 Menu,支持键盘操作以及无障碍体验优化。</p><p>重新设计 Table 筛选和排序按钮的位置,使其归属列更明确。</p><p>Table</p><p>  Table.Summary 支持 sticky 模式。</p><p>  修复有固定列的 Table 内嵌 Table 的额外边距丢失的问题。</p><p>  Table 新增 expandable.fixed 属性用于设置扩展图标固定。</p><p>Upload</p><p>  Upload 组件 itemRender 添加 actions 调用参数。</p><p>  Upload 从拖动事件中删除 stopPropagation,并添加onDrop 回调。</p><p>Typography</p><p>  Typography 增加斜体字支持。</p><p>  修复 Typography 配置 ellipsis={{ suffix: 'xxx' }} 时换行闪动问题。</p><p>Collapse</p><p>  修复 Collapse 不指定 header 时箭头错位的问题。</p><p>  修复 Collapse 隐藏时设置 activeKey 时内容会消失的问题。</p><p>修复 Menu.SubMenu 的 icon 设置为第三方 icon 库时的样式问题。</p><p>修复 Descriptions 单独引入样式丢失的问题。</p><p>Radio.Group 支持 data-* 和 aria-* 属性。</p><p>Statistic.CountDown 组件增加 onChange 事件。</p><p>PageHeader 的 breadcrumb 中允许设置为组件。</p><p>ConfigProvider 支持动态设置 prefixCls。</p><p>修复 Anchor 指定 getCurrentAnchor 后无法触发 onChange 的问题。</p><p>修复 Notification useNotification 生成的通知框 className 作用范围不一致的问题。</p><p>修复 Tabs tabBarGutter 属性失效的问题。#30545</p><p>改写 Space 使用 flexGap 以代替 margin 样式以处理某些边界情况下的布局问题。</p><p>修复 Form 校验错误状态下 Input.Group 和 Cascader 边框颜色错误。</p><p>国际化</p><p>  补充罗马尼亚语国际化。</p><p>  补充荷兰语(荷兰 netherlands)及荷兰语(比利时 belgium)国际化。</p><p>TypeScript</p><p>  Space TypeScript 定义支持 HTMLAttribute 属性。</p>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值