Skip to content

Commit

Permalink
fix(ViewportCollector): detect resizes on the body element
Browse files Browse the repository at this point in the history
Before this change e.g. enabling or disabling the scroll bars,
which change the documentHeight in most of the cases, was not
detected.

The fix requires a the faily new ResizeObserver API which is not
widley supported at the moment. Because this is an edge case that
might not be required for all applications we display a warning
and suggest to add a polyfill in case its an issue so everyone can
decide on their own if its worth it.
  • Loading branch information
garthenweb committed Nov 4, 2018
1 parent e50e69c commit f7d0b85
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 3 deletions.
5 changes: 3 additions & 2 deletions examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ const DisplayViewport = () => {
const { x, y } = useScroll({
priority: 'low',
});
const { documentHeight } = useDimensions({
const { documentHeight, clientWidth } = useDimensions({
priority: 'low',
});
return (
<>
x: {x}, y: {y}, documentHeight: {documentHeight}
x: {x}, y: {y}, documentHeight: {documentHeight}, clientWidth:{' '}
{clientWidth}
</>
);
};
Expand Down
14 changes: 14 additions & 0 deletions lib/ViewportCollector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
browserSupportsPassiveEvents,
simpleDebounce,
debounceOnUpdate,
warnNoResizeObserver,
} from './utils';

import { IDimensions, IScroll, IViewport, OnUpdateType } from './types';
Expand Down Expand Up @@ -121,6 +122,7 @@ export default class ViewportCollector extends React.PureComponent<IProps> {
private lastSyncedDimensionsState: IDimensions;
private tickId: NodeJS.Timer;
private componentMightHaveUpdated: boolean;
private resizeObserver: ResizeObserver | null;

constructor(props: IProps) {
super(props);
Expand All @@ -131,6 +133,7 @@ export default class ViewportCollector extends React.PureComponent<IProps> {
this.dimensionsState = createInitDimensionsState();
this.lastSyncedDimensionsState = { ...this.dimensionsState };
this.lastSyncedScrollState = { ...this.scrollState };
this.resizeObserver = null;
}

componentDidMount() {
Expand All @@ -139,13 +142,24 @@ export default class ViewportCollector extends React.PureComponent<IProps> {
window.addEventListener('resize', this.handleResize, options);
window.addEventListener('orientationchange', this.handleResize, options);

if (typeof window.ResizeObserver !== 'undefined') {
this.resizeObserver = new window.ResizeObserver(this.handleResize);
this.resizeObserver!.observe(document.body);
} else {
warnNoResizeObserver();
}

this.tickId = raf(this.tick);
}

componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll, false);
window.removeEventListener('resize', this.handleResize, false);
window.removeEventListener('orientationchange', this.handleResize, false);
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
raf.cancel(this.tickId);
}

Expand Down
68 changes: 68 additions & 0 deletions lib/modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,71 @@ declare module 'raf' {
};
export = raf;
}

// @see https://gist.github.com/strothj/708afcf4f01dd04de8f49c92e88093c3
interface Window {
ResizeObserver: ResizeObserver;
}

/**
* The ResizeObserver interface is used to observe changes to Element's content
* rect.
*
* It is modeled after MutationObserver and IntersectionObserver.
*/
interface ResizeObserver {
new (callback: ResizeObserverCallback): ResizeObserver;

/**
* Adds target to the list of observed elements.
*/
observe: (target: Element) => void;

/**
* Removes target from the list of observed elements.
*/
unobserve: (target: Element) => void;

/**
* Clears both the observationTargets and activeTargets lists.
*/
disconnect: () => void;
}

/**
* This callback delivers ResizeObserver's notifications. It is invoked by a
* broadcast active observations algorithm.
*/
interface ResizeObserverCallback {
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
}

interface ResizeObserverEntry {
/**
* @param target The Element whose size has changed.
*/
new (target: Element): ResizeObserverEntry;

/**
* The Element whose size has changed.
*/
readonly target: Element;

/**
* Element's content rect when ResizeObserverCallback is invoked.
*/
readonly contentRect: DOMRectReadOnly;
}

interface DOMRectReadOnly {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly top: number;
readonly right: number;
readonly bottom: number;
readonly left: number;

toJSON: () => any;
}
2 changes: 1 addition & 1 deletion lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ export type OnUpdateType = (
options: IViewportCollectorUpdateOptions,
) => void;

export type PriorityType = 'highest' | 'high' | 'normal' | 'low'
export type PriorityType = 'highest' | 'high' | 'normal' | 'low';
9 changes: 9 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,12 @@ document.getElementById('root')
);`,
);
};

export const warnNoResizeObserver = () => {
if (process.env.NODE_ENV === 'production') {
return;
}
console.warn(
'react-viewport-utils: This browser does not support the ResizeObserver API, therefore not all possible resize events will be detected. In most of the cases this is not an issue and can be ignored. If its relevant to your application please consider adding a polyfill, e.g. https://www.npmjs.com/package/resize-observer-polyfill .',
);
};

0 comments on commit f7d0b85

Please sign in to comment.