generated from react-component/footer
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support ResizeObserver.Collection (#70)
* chore: init * chore: single node * chore: single listener * feat: Collection of it * test: Test case fix * test: test for Collection * docs: Update docs * docs: Demo add render info console
- Loading branch information
Showing
13 changed files
with
402 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
## basic | ||
## Basic | ||
|
||
<code src="../../examples/basic.tsx"> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## Collection | ||
|
||
Use `ResizeObserver.Collection` to collect multiple `ResizeObserver` resize event within frame. | ||
|
||
<code src="../../examples/collection.tsx"> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import '../assets/index.less'; | ||
import React from 'react'; | ||
import ResizeObserver from '../src'; | ||
|
||
function randomSize() { | ||
return { | ||
width: Math.round(50 + Math.random() * 150), | ||
height: Math.round(50 + Math.random() * 150), | ||
}; | ||
} | ||
|
||
const sharedStyle: React.CSSProperties = { | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
color: '#fff', | ||
}; | ||
|
||
export default function App() { | ||
const [size1, setSize1] = React.useState(randomSize()); | ||
const [size2, setSize2] = React.useState(randomSize()); | ||
|
||
console.log('Render:', size1, size2); | ||
|
||
return ( | ||
<ResizeObserver.Collection | ||
onBatchResize={infoList => { | ||
console.log( | ||
'Batch Resize:', | ||
infoList, | ||
infoList.map(({ data, size }) => `${data}(${size.width}/${size.height})`), | ||
); | ||
}} | ||
> | ||
<div style={{ display: 'flex', columnGap: 4, marginBottom: 8 }}> | ||
<button onClick={() => setSize1(randomSize())}>Resize: 1</button> | ||
<button onClick={() => setSize2(randomSize())}>Resize: 2</button> | ||
<button | ||
onClick={() => { | ||
setSize1(randomSize()); | ||
setSize2(randomSize()); | ||
}} | ||
> | ||
Resize: all | ||
</button> | ||
</div> | ||
<div style={{ display: 'flex', columnGap: 16 }}> | ||
<ResizeObserver data="shape_1"> | ||
<div style={{ ...sharedStyle, ...size1, background: 'red' }}>1</div> | ||
</ResizeObserver> | ||
<ResizeObserver data="shape_2"> | ||
<div style={{ ...sharedStyle, ...size2, background: 'blue' }}>2</div> | ||
</ResizeObserver> | ||
</div> | ||
</ResizeObserver.Collection> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import * as React from 'react'; | ||
import type { SizeInfo } from '.'; | ||
|
||
type onCollectionResize = (size: SizeInfo, element: HTMLElement, data: any) => void; | ||
|
||
export const CollectionContext = React.createContext<onCollectionResize>(null); | ||
|
||
export interface ResizeInfo { | ||
size: SizeInfo; | ||
data: any; | ||
element: HTMLElement; | ||
} | ||
|
||
export interface CollectionProps { | ||
/** Trigger when some children ResizeObserver changed. Collect by frame render level */ | ||
onBatchResize?: (resizeInfo: ResizeInfo[]) => void; | ||
children?: React.ReactNode; | ||
} | ||
|
||
/** | ||
* Collect all the resize event from children ResizeObserver | ||
*/ | ||
export 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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as React from 'react'; | ||
|
||
export interface DomWrapperProps { | ||
children: React.ReactElement; | ||
} | ||
|
||
/** | ||
* Fallback to findDOMNode if origin ref do not provide any dom element | ||
*/ | ||
export default class DomWrapper extends React.Component<DomWrapperProps> { | ||
render() { | ||
return this.props.children; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { composeRef, supportRef } from 'rc-util/lib/ref'; | ||
import * as React from 'react'; | ||
import findDOMNode from 'rc-util/lib/Dom/findDOMNode'; | ||
import { observe, unobserve } from '../utils/observerUtil'; | ||
import type { ResizeObserverProps } from '..'; | ||
import DomWrapper from './DomWrapper'; | ||
import { CollectionContext } from '../Collection'; | ||
|
||
export interface SingleObserverProps extends ResizeObserverProps { | ||
children: React.ReactElement; | ||
} | ||
|
||
export default function SingleObserver(props: SingleObserverProps) { | ||
const { children, disabled } = props; | ||
const elementRef = React.useRef<Element>(null); | ||
const wrapperRef = React.useRef<DomWrapper>(null); | ||
|
||
const onCollectionResize = React.useContext(CollectionContext); | ||
|
||
// ============================= Size ============================= | ||
const sizeRef = React.useRef({ | ||
width: 0, | ||
height: 0, | ||
offsetWidth: 0, | ||
offsetHeight: 0, | ||
}); | ||
|
||
// ============================= Ref ============================== | ||
const canRef = React.isValidElement(children) && supportRef(children); | ||
const originRef: React.Ref<Element> = canRef ? (children as any).ref : null; | ||
|
||
const mergedRef = React.useMemo( | ||
() => composeRef<Element>(originRef, elementRef), | ||
[originRef, elementRef], | ||
); | ||
|
||
// =========================== Observe ============================ | ||
const propsRef = React.useRef<SingleObserverProps>(props); | ||
propsRef.current = props; | ||
|
||
// Handler | ||
const onInternalResize = React.useCallback((target: HTMLElement) => { | ||
const { onResize, data } = propsRef.current; | ||
|
||
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); | ||
}); | ||
} | ||
} | ||
}, []); | ||
|
||
// Dynamic observe | ||
React.useEffect(() => { | ||
const currentElement: HTMLElement = | ||
findDOMNode(elementRef.current) || findDOMNode(wrapperRef.current); | ||
|
||
if (currentElement && !disabled) { | ||
observe(currentElement, onInternalResize); | ||
} | ||
|
||
return () => unobserve(currentElement, onInternalResize); | ||
}, [elementRef.current, disabled]); | ||
|
||
// ============================ Render ============================ | ||
if (canRef) { | ||
return ( | ||
<DomWrapper ref={wrapperRef}> | ||
{React.cloneElement(children as any, { | ||
ref: mergedRef, | ||
})} | ||
</DomWrapper> | ||
); | ||
} | ||
return children; | ||
} |
Oops, something went wrong.
9fe9d1e
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: