固钉其实按照我自己的理解就是用固定定位将其定位到某个位置。很简单的一个效果。
antd-affix我认为其核心可以概括为几点:
组件加载滚动监听,组件销毁销毁监听。
利用一个元素在原本元素位置占位。当达到固定元素的条件。元素占位,反之。相反。
监听占位元素、宽度高度样式的更改,有更改就去重置渲染参数条件。
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();
}
页面加载创建了一个定时器并且在window上添加了一个滚动监听事件addObserveTarget
addObserveTarget函数内部,调用了当前组件的lazyUpdatePosition,去计算出fixedtop的值是否达到offsetTop,达到了就直接返回。没达到就重置。
而this.setState操作都会牵动componentDIdMount执行。最后都会去执行 this.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举例,