赞
踩
github 地址:https://github.com/devrelm/resize-observer
本地启动
npm install
npm start
node 18.16.0 (npm 9.5.1) 启动失败报错
node:internal/crypto/hash:71
this[kHandle] = new _Hash(algorithm, xofLen);
^
Error: error:0308010C:digital envelope routines::unsupported
解决:更改 node 版本
node 16.16.0 (npm 8.11.0) 启动成功
const onResize: ResizeObserverProps["onResize"] = ({ width, height, offsetHeight, offsetWidth, }) => { setTimes((prevTimes) => prevTimes + 1); console.log( "Resize:", "\n", "BoundingBox", width, height, "\n", "Offset", offsetWidth, offsetHeight ); }; <ResizeObserver onResize={onResize} disabled={disabled}> <Wrapper> <textarea ref={textareaRef} placeholder="I'm a textarea!" /> </Wrapper> </ResizeObserver>;
export type OnResize = (size: SizeInfo, element: HTMLElement) => void;
export interface ResizeObserverProps {
/** Pass to ResizeObserver.Collection with additional data */
data?: any;
children:
| React.ReactNode
| ((ref: React.RefObject<any>) => React.ReactElement);
disabled?: boolean;
/** Trigger if element resized. Will always trigger when first time render. */
onResize?: OnResize;
}
真正组件在ResizeObserver组件
const RefResizeObserver = React.forwardRef(ResizeObserver) as React.ForwardRefExoticComponent<
React.PropsWithoutRef<ResizeObserverProps> & React.RefAttributes<any>
> & {
Collection: typeof Collection;
};
RefResizeObserver.Collection = Collection;
export default RefResizeObserver;
里面还有一层组件SingleObserver
//src\index.tsx
function ResizeObserver(props: ResizeObserverProps, ref: React.Ref<HTMLElement>) {
return childNodes.map((child, index) => {
const key = child?.key || `${INTERNAL_PREFIX_KEY}-${index}`;
return (
<SingleObserver {...props} key={key} ref={index === 0 ? ref : undefined}>
{child}
</SingleObserver>
);
}) as any as React.ReactElement;
}
真正实现观察的方法在这个组件
const RefSingleObserver = React.forwardRef(SingleObserver);
//src\SingleObserver\index.tsx
function SingleObserver(
props: SingleObserverProps,
ref: React.Ref<HTMLElement>
) {
return (
<DomWrapper ref={wrapperRef}>
{canRef
? React.cloneElement(mergedChildren as any, {
ref: mergedRef,
})
: mergedChildren}
</DomWrapper>
);
}
监听 elementRef.current 的變化
在 SingleObserver 组件
import { observe, unobserve } from "../utils/observerUtil";
// Dynamic observe
React.useEffect(() => {
// getDom获取要被侦听的element
const currentElement: HTMLElement = getDom();
if (currentElement && !disabled) {
// 执行侦听
observe(currentElement, onInternalResize);
}
// 清除侦听
return () => unobserve(currentElement, onInternalResize);
}, [elementRef.current, disabled]);
// src\utils\observerUtil.ts const elementListeners = new Map<Element, Set<ResizeListener>>(); import ResizeObserver from 'resize-observer-polyfill'; // interface ResizeObserverEntry { // readonly target: Element; // readonly contentRect: DOMRectReadOnly; // } // onResize 创建侦听器传入的callback function onResize(entities: ResizeObserverEntry[]) { entities.forEach((entity) => { const { target } = entity; // elementListeners.get(target)是set集合 ,listener是回调函数onInternalResize elementListeners.get(target)?.forEach((listener) => listener(target)); }); } // Note: ResizeObserver polyfill not support option to measure border-box resize const resizeObserver = new ResizeObserver(onResize);
// resize-observer-polyfill中ResizeObserverSPI类
const observer = new ResizeObserverSPI(callback, controller, this);
ResizeObserverSPI
类的broadcastActive
方法
callback
返回的信息,entries
是一个数组,返回所有正在活跃的目标element列表
// Create ResizeObserverEntry instance for every active observation.
const entries = this.activeObservations_.map((observation) => {
// 返回被觀察element最新的大小
return new ResizeObserverEntry(
observation.target,
// 執行observation.broadcastRect函數獲取最新的大小
observation.broadcastRect()
);
});
// 改变回调函数的this指向ctx
this.callback_.call(ctx, entries, ctx);
const elementListeners = new Map<Element, Set<ResizeListener>>();
function observe(element: Element, callback: ResizeListener) {
if (!elementListeners.has(element)) {
// 给elementListeners添加一个键值对
elementListeners.set(element, new Set());
//
resizeObserver.observe(element);
}
// elementListeners.get(element) 是set结构,给set插入一个新元素callback回调函数即onInternalResize
elementListeners.get(element).add(callback);
}
const elementListeners = new Map<Element, Set<ResizeListener>>(); // 取消侦听 function unobserve(element: Element, callback: ResizeListener) { if (elementListeners.has(element)) { //set集合移除callback回调函数 elementListeners.get(element).delete(callback); if (!elementListeners.get(element).size) { // 取消侦听 resizeObserver.unobserve(element); // 移除目标element elementListeners.delete(element); } } }
CollectionContext = React.createContext<onCollectionResize>(null);
const onCollectionResize = React.useContext(CollectionContext);
const propsRef = React.useRef < SingleObserverProps > props; propsRef.current = props; // Handler const onInternalResize = React.useCallback((target: HTMLElement) => { const { onResize, data } = propsRef.current; // getBoundingClientRect侦听器内部实现的一个方法,获取元素尺寸大小 const { width, height } = target.getBoundingClientRect(); const { offsetWidth, offsetHeight } = target; /** * Resize observer trigger when content size changed. * In most case we just care about element size, * let's use `boundary` instead of `contentRect` here to avoid shaking. */ const fixedWidth = Math.floor(width); const fixedHeight = Math.floor(height); if ( sizeRef.current.width !== fixedWidth || sizeRef.current.height !== fixedHeight || sizeRef.current.offsetWidth !== offsetWidth || sizeRef.current.offsetHeight !== offsetHeight ) { const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight, }; sizeRef.current = size; // IE is strange, right? const mergedOffsetWidth = offsetWidth === Math.round(width) ? width : offsetWidth; const mergedOffsetHeight = offsetHeight === Math.round(height) ? height : offsetHeight; const sizeInfo = { ...size, offsetWidth: mergedOffsetWidth, offsetHeight: mergedOffsetHeight, }; // Let collection know what happened onCollectionResize?.(sizeInfo, target, data); if (onResize) { // defer the callback but not defer to next frame Promise.resolve().then(() => { // 给父组件传递信息 onResize(sizeInfo, target); }); } } }, []);
const getDom = () =>
findDOMNode<HTMLElement>(elementRef.current) ||
// Support `nativeElement` format
(elementRef.current && typeof elementRef.current === 'object'
? findDOMNode<HTMLElement>((elementRef.current as any)?.nativeElement)
: null) ||
findDOMNode<HTMLElement>(wrapperRef.current);
github:https://github.com/react-component/util/blob/master/src/Dom/findDOMNode.ts
/** * Return if a node is a DOM node. Else will return by `findDOMNode` */ function findDOMNode<T = Element | Text>( node: React.ReactInstance | HTMLElement | SVGElement, ): T { if (isDOM(node)) { return (node as unknown) as T; } if (node instanceof React.Component) { return (ReactDOM.findDOMNode(node) as unknown) as T; } return null; }
function isDOM(node: any): node is HTMLElement | SVGElement {
// https://developer.mozilla.org/en-US/docs/Web/API/Element
// Since XULElement is also subclass of Element, we only need HTMLElement and SVGElement
return node instanceof HTMLElement || node instanceof SVGElement;
}
function Collection({ children, onBatchResize }: CollectionProps) { const resizeIdRef = React.useRef(0); const resizeInfosRef = React.useRef<ResizeInfo[]>([]); const onCollectionResize = React.useContext(CollectionContext); const onResize = React.useCallback<onCollectionResize>( (size, element, data) => { resizeIdRef.current += 1; const currentId = resizeIdRef.current; resizeInfosRef.current.push({ size, element, data, }); Promise.resolve().then(() => { if (currentId === resizeIdRef.current) { onBatchResize?.(resizeInfosRef.current); resizeInfosRef.current = []; } }); // Continue bubbling if parent exist onCollectionResize?.(size, element, data); }, [onBatchResize, onCollectionResize], ); return <CollectionContext.Provider value={onResize}>{children}</CollectionContext.Provider>; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。