资讯专栏INFORMATION COLUMN

antd源码解读(6)- Affix

coordinate35 / 2023人阅读

摘要:这个组件是一个图钉组件,使用的布局,让组件固定在窗口的某一个位置上,并且可以在到达指定位置的时候才去固定。

Affix

这个组件是一个图钉组件,使用的fixed布局,让组件固定在窗口的某一个位置上,并且可以在到达指定位置的时候才去固定。

AffixProps

还是老样子,看一个组件首先我们先来看看他可以传入什么参数

  // Affix
  export interface AffixProps {
    /**
    * 距离窗口顶部达到指定偏移量后触发
    */
    offsetTop?: number;
    offset?: number;
    /** 距离窗口底部达到指定偏移量后触发 */
    offsetBottom?: number;
    style?: React.CSSProperties;
    /** 固定状态改变时触发的回调函数 */
    onChange?: (affixed?: boolean) => void;
    /** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
    target?: () => Window | HTMLElement;
    // class样式命名空间,可以定义自己的样式命名
    prefixCls?: string;
  }
Render()

看完传入参数之后,就到入口函数看看这里用到了什么参数

  render() {
    // 构造当前组件的class样式
    const className = classNames({
      [this.props.prefixCls || "ant-affix"]: this.state.affixStyle,
    });
    // 这里和之前看的一样,忽略掉props中的在div标签上面不需要的一些属性
    // 但是貌似没有去掉offset,后面我还查了一下DIV上面能不能有offset
    // 但是没看见有offset,只看见offsetLeft, offsetHeight....
    const props = omit(this.props, ["prefixCls", "offsetTop", "offsetBottom", "target", "onChange"]);
    const placeholderStyle = { ...this.state.placeholderStyle, ...this.props.style };
    return (
      // 注意咯 看这里placeholder的作用了 如图
      // 这里的placeholder的作用是当这个组件样式变为fixed的时候,
      // 会脱离文档流,然后导致原本的dom结构变化,宽高都会有所变化
      // 所以这是后放一个占位元素来顶住这一组件脱离文档流的时候的影响
      
{this.props.children}
); }

完整代码
  import React from "react";
  import ReactDOM from "react-dom";
  import PropTypes from "prop-types";
  import addEventListener from "rc-util/lib/Dom/addEventListener";
  import classNames from "classnames";
  import shallowequal from "shallowequal";
  import omit from "omit.js";
  import getScroll from "../_util/getScroll";
  import { throttleByAnimationFrameDecorator } from "../_util/throttleByAnimationFrame";

  function getTargetRect(target): ClientRect {
    return target !== window ?
      target.getBoundingClientRect() :
      { top: 0, left: 0, bottom: 0 };
  }

  function getOffset(element: HTMLElement, target) {
    const elemRect = element.getBoundingClientRect();
    const targetRect = getTargetRect(target);

    const scrollTop = getScroll(target, true);
    const scrollLeft = getScroll(target, false);

    const docElem = window.document.body;
    const clientTop = docElem.clientTop || 0;
    const clientLeft = docElem.clientLeft || 0;

    return {
      top: elemRect.top - targetRect.top +
        scrollTop - clientTop,
      left: elemRect.left - targetRect.left +
        scrollLeft - clientLeft,
      width: elemRect.width,
      height: elemRect.height,
    };
  }

  function noop() {}

  function getDefaultTarget() {
    return typeof window !== "undefined" ?
      window : null;
  }

  // Affix
  export interface AffixProps {
    /**
    * 距离窗口顶部达到指定偏移量后触发
    */
    offsetTop?: number;
    offset?: number;
    /** 距离窗口底部达到指定偏移量后触发 */
    offsetBottom?: number;
    style?: React.CSSProperties;
    /** 固定状态改变时触发的回调函数 */
    onChange?: (affixed?: boolean) => void;
    /** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
    target?: () => Window | HTMLElement;
    prefixCls?: string;
  }

  export default class Affix extends React.Component {
    static propTypes = {
      offsetTop: PropTypes.number,
      offsetBottom: PropTypes.number,
      target: PropTypes.func,
    };

    scrollEvent: any;
    resizeEvent: any;
    timeout: any;
    refs: {
      fixedNode: HTMLElement;
    };

    events = [
      "resize",
      "scroll",
      "touchstart",
      "touchmove",
      "touchend",
      "pageshow",
      "load",
    ];

    eventHandlers = {};

    constructor(props) {
      super(props);
      this.state = {
        affixStyle: null,
        placeholderStyle: null,
      };
    }

    setAffixStyle(e, affixStyle) {
      const { onChange = noop, target = getDefaultTarget } = this.props;
      const originalAffixStyle = this.state.affixStyle;
      const isWindow = target() === window;
      if (e.type === "scroll" && originalAffixStyle && affixStyle && isWindow) {
        return;
      }
      if (shallowequal(affixStyle, originalAffixStyle)) {
        return;
      }
      this.setState({ affixStyle }, () => {
        const affixed = !!this.state.affixStyle;
        if ((affixStyle && !originalAffixStyle) ||
            (!affixStyle && originalAffixStyle)) {
          onChange(affixed);
        }
      });
    }

    setPlaceholderStyle(placeholderStyle) {
      const originalPlaceholderStyle = this.state.placeholderStyle;
      if (shallowequal(placeholderStyle, originalPlaceholderStyle)) {
        return;
      }
      this.setState({ placeholderStyle });
    }

    @throttleByAnimationFrameDecorator()
    updatePosition(e) {
      let { offsetTop, offsetBottom, offset, target = getDefaultTarget } = this.props;
      const targetNode = target();

      // Backwards support
      offsetTop = offsetTop || offset;
      const scrollTop = getScroll(targetNode, true);
      const affixNode = ReactDOM.findDOMNode(this) as HTMLElement;
      const elemOffset = getOffset(affixNode, targetNode);
      const elemSize = {
        width: this.refs.fixedNode.offsetWidth,
        height: this.refs.fixedNode.offsetHeight,
      };

      const offsetMode = {
        top: false,
        bottom: false,
      };
      // Default to `offsetTop=0`.
      if (typeof offsetTop !== "number" && typeof offsetBottom !== "number") {
        offsetMode.top = true;
        offsetTop = 0;
      } else {
        offsetMode.top = typeof offsetTop === "number";
        offsetMode.bottom = typeof offsetBottom === "number";
      }

      const targetRect = getTargetRect(targetNode);
      const targetInnerHeight =
        (targetNode as Window).innerHeight || (targetNode as HTMLElement).clientHeight;
      if (scrollTop > elemOffset.top - (offsetTop as number) && offsetMode.top) {
        // Fixed Top
        const width = elemOffset.width;
        this.setAffixStyle(e, {
          position: "fixed",
          top: targetRect.top + (offsetTop as number),
          left: targetRect.left + elemOffset.left,
          width,
        });
        this.setPlaceholderStyle({
          width,
          height: elemSize.height,
        });
      } else if (
        scrollTop < elemOffset.top + elemSize.height + (offsetBottom as number) - targetInnerHeight &&
          offsetMode.bottom
      ) {
        // Fixed Bottom
        const targetBottomOffet = targetNode === window ? 0 : (window.innerHeight - targetRect.bottom);
        const width = elemOffset.width;
        this.setAffixStyle(e, {
          position: "fixed",
          bottom: targetBottomOffet + (offsetBottom as number),
          left: targetRect.left + elemOffset.left,
          width,
        });
        this.setPlaceholderStyle({
          width,
          height: elemOffset.height,
        });
      } else {
        const { affixStyle } = this.state;
        if (e.type === "resize" && affixStyle && affixStyle.position === "fixed" && affixNode.offsetWidth) {
          this.setAffixStyle(e, { ...affixStyle, width: affixNode.offsetWidth });
        } else {
          this.setAffixStyle(e, null);
        }
        this.setPlaceholderStyle(null);
      }
    }

    componentDidMount() {
      const target = this.props.target || getDefaultTarget;
      // Wait for parent component ref has its value
      this.timeout = setTimeout(() => {
        this.setTargetEventListeners(target);
      });
    }

    componentWillReceiveProps(nextProps) {
      if (this.props.target !== nextProps.target) {
        this.clearEventListeners();
        this.setTargetEventListeners(nextProps.target);

        // Mock Event object.
        this.updatePosition({});
      }
    }

    componentWillUnmount() {
      this.clearEventListeners();
      clearTimeout(this.timeout);
      (this.updatePosition as any).cancel();
    }

    setTargetEventListeners(getTarget) {
      const target = getTarget();
      if (!target) {
        return;
      }
      this.clearEventListeners();

      this.events.forEach(eventName => {
        this.eventHandlers[eventName] = addEventListener(target, eventName, this.updatePosition);
      });
    }

    clearEventListeners() {
      this.events.forEach(eventName => {
        const handler = this.eventHandlers[eventName];
        if (handler && handler.remove) {
          handler.remove();
        }
      });
    }

    render() {
      const className = classNames({
        [this.props.prefixCls || "ant-affix"]: this.state.affixStyle,
      });

      const props = omit(this.props, ["prefixCls", "offsetTop", "offsetBottom", "target", "onChange"]);
      const placeholderStyle = { ...this.state.placeholderStyle, ...this.props.style };
      return (
        
{this.props.children}
); } }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89188.html

相关文章

  • antd源码解读(2)- Icon

    摘要:作为开发当中使用相对频繁的一个组件,其实现也很简单,但是其中比较麻烦的一部分是字体的制作,可以参看这篇文章。接口中的种属性方法,不属于上述六种。为事件属性,可以大家也可以根据上面所提供的制作的方法和这样的方式来实现自己的组件 Icon icon作为开发当中使用相对频繁的一个组件,其实现也很简单,但是其中比较麻烦的一部分是icon字体的制作,可以参看这篇文章。 Antd的Icon组件使用...

    fjcgreat 评论0 收藏0
  • antd源码解读(1)-index.js

    github: 地址gitbook: 地址 Index.js 看一个代码的时候首先当然是从他的入口文件开始看起,所以第一份代码我们看的是/index.js文件 开始 打开index.js文件,代码只有28行,其中包含了一个camelCase函数(看函数名就知道这是个给名称进行驼峰命名法的函数),一个req变量,以及这个的变量操作和export操作 在这个文件里面我首先查了require.conte...

    zeyu 评论0 收藏0
  • antd源码解读(二)Tooltip组件解析

    摘要:结构项目结构如下,负责外层封装,负责事件绑定与渲染控制。节点通过决定事件绑定情况,即通过属性绑定事件情况,事件控制组件的属性,这里就不详细说了。为隐藏状态下的添加的,并包裹懒加载组件。 引言 antd的Tooltip组件在react-componment/trigger的基础上进行封装,而组件Popover和Popconfirm是使用Tooltip组件的进行pop,在react-com...

    fanux 评论0 收藏0
  • antd源码解析(一)Form组件解析

    摘要:引言看过源码的都知道,其实是在一组组件的基础上进行了一层封装,本文主要解读组件的基础组件,另外会略过模式下的代码。解读源码首先要从自己最常用的或者感兴趣的入手,首先组件最主要的还是在这个装饰器入手。 引言 看过antd源码的都知道,antd其实是在一组react-componment组件的基础上进行了一层ui封装,本文主要解读antd组件Form的基础组件react-componmen...

    timger 评论0 收藏0
  • antd源码解读(3)- Button

    Button Button包括了两个组件,Button与ButtonGroup。 ButtonProps 看一个组件首先看的是他的传参也就是props,所以我们这里先看Button组件的ButtonProps export type ButtonType = primary | ghost | dashed | danger; export type ButtonShape = circl...

    miguel.jiang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<