antd源码-Affix固钉解析

antd源码-Affix固钉解析

固钉其实按照我自己的理解就是用固定定位将其定位到某个位置。很简单的一个效果。

antd-affix我认为其核心可以概括为几点:

  1. 组件加载滚动监听,组件销毁销毁监听。

  2. 利用一个元素在原本元素位置占位。当达到固定元素的条件。元素占位,反之。相反。

  3. 监听占位元素、宽度高度样式的更改,有更改就去重置渲染参数条件。

render函数
  render() {
        const { affixStyle, placeholderStyle } = this.state;
        console.log("affixStyle",this.state)
    let props = omit(this.props, ['offsetTop', 'offsetBottom', 'target', 'onChange']);
        const {children,className}=this.props;
        const AffixclassName = classNames(
           { 'ant-affix':affixStyle},
            className
            );
        return (
            <ResizeObserver
        onResize={() => {
         this.updatePosition();
        }}
      >
           <div ref={this.savePlaceholderNode}>
            {affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
          <div className={AffixclassName} ref={this.saveFixedNode}  style={affixStyle}>
          <ResizeObserver
              onResize={() => {
              this.updatePosition();
              }}
            >
              {children}
      </ResizeObserver>
          </div>
          </div>
      </ResizeObserver>
        )
    }
       prepareMeasure = () => {
        // event param is used before. Keep compatible ts define here.
        this.setState({
          status: AffixStatus.Prepare,
          affixStyle: undefined,
          placeholderStyle: undefined,
        });
    };

    updatePosition() {
        this.prepareMeasure();
    }

从这段代码可以看到的是引用了ResizeObserver,这个组件的功能就是在改变宽度和高度的时候,会触发,做的事情就很简单,初始化状态和固定样式和占位样式,页面的元素就恢复到最初了。

在看初始化做了什么

    componentDidMount(){
        const { target } = this.props;
        console.log(target)
    if (target) {
      // [Legacy] Wait for parent component ref has its value.
      // We should use target as directly element instead of function which makes element check hard.
        // 
      this.timeout = setTimeout(() => {
        addObserveTarget(target(), this);
        // Mock Event object.
        this.updatePosition();
      });
    }
      }
    componentDidUpdate(prevProps:AffixProps){
      const { prevTarget } = this.state;
      const { target } = this.props;
      let newTarget = null;
      if (target) {
        newTarget = target() || null;
      }
  
      if (prevTarget !== newTarget) {
        removeObserveTarget(this);
        if (newTarget) {
          addObserveTarget(newTarget, this);
          // Mock Event object.
          this.updatePosition();
        }
  
        this.setState({ prevTarget: newTarget });
      }
      if (
        prevProps.offsetTop !== this.props.offsetTop ||
        prevProps.offsetBottom !== this.props.offsetBottom
      ) {
        this.updatePosition();
      }
  
        this.measure();
    }
 lazyUpdatePosition() {
    const { target } = this.props;
    const { affixStyle } = this.state;

    // Check position change before measure to make Safari smooth
    if (target && affixStyle) {
      const offsetTop = this.getOffsetTop();
      const offsetBottom = this.getOffsetBottom();

      const targetNode = target();
      if (targetNode && this.placeholderNode) {
        const targetRect = getTargetRect(targetNode);
        const placeholderReact = getTargetRect(this.placeholderNode);
        const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
        const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
        
        if (
          (fixedTop !== undefined && affixStyle.top === fixedTop) ||
          (fixedBottom !== undefined && affixStyle.bottom === fixedBottom)
        ) {
          return;
        }
      }
    }

    // Directly call prepare measure since it's already throttled.
    this.prepareMeasure();
  }
  1. 页面加载创建了一个定时器并且在window上添加了一个滚动监听事件addObserveTarget

  2. addObserveTarget函数内部,调用了当前组件的lazyUpdatePosition,去计算出fixedtop的值是否达到offsetTop,达到了就直接返回。没达到就重置。

  3. 而this.setState操作都会牵动componentDIdMount执行。最后都会去执行 this.measure();

measure
 measure = () => {
    const { status, lastAffix } = this.state;
    const { target, onChange } = this.props;
    if (status !== AffixStatus.Prepare || !this.fixedNode || !this.placeholderNode || !target) {
      return;
    }

    const offsetTop = this.getOffsetTop();
    const offsetBottom = this.getOffsetBottom();

    const targetNode = target();
    if (!targetNode) {
      return;
    }

    const newState: Partial<AffixState> = {
      status: AffixStatus.None,
    };
    const targetRect = getTargetRect(targetNode);
    const placeholderReact = getTargetRect(this.placeholderNode);
    const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
    const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);

    if (fixedTop !== undefined) {
      newState.affixStyle = {
        position: 'fixed',
        top: fixedTop,
        width: placeholderReact.width,
        height: placeholderReact.height,
      };
      newState.placeholderStyle = {
        width: placeholderReact.width,
        height: placeholderReact.height,
      };
    } else if (fixedBottom !== undefined) {
      newState.affixStyle = {
        position: 'fixed',
        bottom: fixedBottom,
        width: placeholderReact.width,
        height: placeholderReact.height,
      };
      newState.placeholderStyle = {
        width: placeholderReact.width,
        height: placeholderReact.height,
      };
    }

    newState.lastAffix = !!newState.affixStyle;
    if (onChange && lastAffix !== newState.lastAffix) {
      onChange(newState.lastAffix);
    }

    this.setState(newState as AffixState);
  };
export function getTargetRect(target: BindElement): ClientRect {
  return target !== window
    ? (target as HTMLElement).getBoundingClientRect()
    : ({ top: 0, bottom: window.innerHeight } as ClientRect);
}

export function getFixedTop(
  placeholderReact: Rect,
  targetRect: Rect,
  offsetTop: number | undefined,
) {
  if (offsetTop !== undefined && targetRect.top > placeholderReact.top - offsetTop) {
    return offsetTop + targetRect.top;
  }
  return undefined;
}

拿我们传递了offsetTop=20举例,

  • getTargetRect 这个函数传递window的话。永远top为0,bottom为window的高,传递的是dom元素的话,getBoundingClientRect会返回这个元素的宽高位置信息,
  • 关注getFixedTop,我们没有指定target的时候,targetRect为window,,top永远为0,placeholderReact为占位元素,他的top为初始化元素的top,offsetTop是我们传递的,得到是undefined就初始化,否则继续保持为固定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值