Affix 固钉
- 需要考虑性能问题 刷新频率 实例个数
- 需要考虑计算高度问题 getBoundingClientRect 获取元素边界 宽高等
- 需要考虑固定时需要有占位元素替代原先位置 避免出现断层
- 需要考虑监听事件列表 ‘resize’, ‘scroll’, ‘touchstart’, ‘touchmove’, ‘touchend’, ‘pageshow’, ‘load’,
- 需要考虑销毁组件时清空事件监听
- 需要考虑的元素自身属性 高度宽度变化等 ResizeObserver
节流防抖
底层刷新回调处理
// 16 / 1000 60帧率
// 在16ms之后 处理完当前任务 清空当前标记 开始接受下次任务处理
// 防抖频率 在60帧
let raf = (callback: FrameRequestCallback) => +setTimeout(callback, 16);
let caf = (num: number) => clearTimeout(num);
// 当你准备更新动画时你应该调用此方法。
// 这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。
// 回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,
// 回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) {
raf = (callback: FrameRequestCallback) =>
window.requestAnimationFrame(callback);
caf = (handle: number) => window.cancelAnimationFrame(handle);
}
export function wrapperRaf(callback: () => void): number {
return raf(callback);
}
wrapperRaf.cancel = caf;
真正控制任务频率的地方,通过控制更新requestId来控制操作频率
export function throttleByAnimationFrame(fn: (...args: any[]) => void) {
let requestId: number | null;
// eslint-disable-next-line
const later = (args: any[]) => () => {
requestId = null;
fn(...args);
};
// eslint-disable-next-line
const throttled = (...args: any[]) => {
if (requestId == null) {
requestId = wrapperRaf(later(args));
}
};
// eslint-disable-next-line
(throttled as any).cancel = () => wrapperRaf.cancel(requestId!);
return throttled;
}
事件引发的刷新
在上述事件集合中 ‘resize’, ‘scroll’, ‘touchstart’, ‘touchmove’, ‘touchend’, ‘pageshow’, ‘load’,并不会立马执行更新位置,而是触发state发生变化,且打上需要重新计算的标记,触发组件render之后,执行componentDidUpdate进行位置的重新计算
两层ResizeObserver
原因猜测是 一个照顾原始dom变化跟踪,一个是fixed dom跟踪 要相互同步,比如dom的宽高变化
<ResizeObserver
onResize={() => {
this.updatePosition();
}}
>
<div {...props} ref={this.savePlaceholderNode}>
{affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
<div className={className} ref={this.saveFixedNode} style={affixStyle}>
<ResizeObserver
onResize={() => {
this.updatePosition();
}}
>
{children}
</ResizeObserver>
</div>
</div>
</ResizeObserver>