[change] call 'onLayout' when elements are resized

Uses ResizeObserver to monitor layout changes. Falls back to
window.onresize (with initial firing).

Fix #60
Close #848
This commit is contained in:
Giuseppe Gurgone
2018-03-02 23:31:25 +01:00
committed by Nicolas Gallagher
parent 9427eea293
commit 5a04d07a35
6 changed files with 152 additions and 20 deletions

View File

@@ -9,6 +9,7 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import debounce from 'debounce';
import findNodeHandle from '../../exports/findNodeHandle';
const emptyObject = {};
const registry = {};
@@ -16,22 +17,68 @@ const registry = {};
let id = 1;
const guid = () => `r-${id++}`;
let resizeObserver;
if (canUseDOM) {
const triggerAll = () => {
Object.keys(registry).forEach(key => {
const instance = registry[key];
instance._handleLayout();
if (typeof window.ResizeObserver !== 'undefined') {
resizeObserver = new window.ResizeObserver(entries => {
entries.forEach(({ target }) => {
const instance = registry[target._onLayoutId];
instance && instance._handleLayout();
});
});
};
} else {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
console.warn(
'onLayout relies on ResizeObserver which is not supported by your browser. ' +
'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill. ' +
'Falling back to window.onresize.'
);
}
window.addEventListener('resize', debounce(triggerAll, 16), false);
const triggerAll = () => {
Object.keys(registry).forEach(key => {
const instance = registry[key];
instance._handleLayout();
});
};
window.addEventListener('resize', debounce(triggerAll, 16), false);
}
}
const observe = instance => {
const id = guid();
registry[id] = instance;
if (resizeObserver) {
const node = findNodeHandle(instance);
node._onLayoutId = id;
resizeObserver.observe(node);
} else {
const id = guid();
instance._onLayoutId = id;
instance._handleLayout();
}
};
const unobserve = instance => {
delete registry[instance._onLayoutId];
if (resizeObserver) {
const node = findNodeHandle(instance);
delete node._onLayoutId;
resizeObserver.unobserve(node);
} else {
delete instance._onLayoutId;
}
};
const safeOverride = (original, next) => {
if (original) {
return function prototypeOverride() {
original.call(this);
next.call(this);
/* eslint-disable prefer-rest-params */
original.call(this, arguments);
next.call(this, arguments);
/* eslint-enable prefer-rest-params */
};
}
return next;
@@ -47,16 +94,20 @@ const applyLayout = Component => {
function componentDidMount() {
this._layoutState = emptyObject;
this._isMounted = true;
this._onLayoutId = guid();
registry[this._onLayoutId] = this;
this._handleLayout();
observe(this);
}
);
Component.prototype.componentDidUpdate = safeOverride(
componentDidUpdate,
function componentDidUpdate() {
this._handleLayout();
function componentDidUpdate(prevProps) {
if (this.props.onLayout && !prevProps.onLayout) {
observe(this);
} else if (!this.props.onLayout && prevProps.onLayout) {
unobserve(this);
} else if (!resizeObserver) {
this._handleLayout();
}
}
);
@@ -64,7 +115,7 @@ const applyLayout = Component => {
componentWillUnmount,
function componentWillUnmount() {
this._isMounted = false;
delete registry[this._onLayoutId];
unobserve(this);
}
);

View File

@@ -2,8 +2,9 @@
This guide will help you to use and test React Native for Web once it has been installed.
Your application may need to polyfill `Promise`, `Object.assign`, and
`Array.from` as necessary for your desired browser support.
Your application may need to polyfill `Promise`, `Object.assign`, `Array.from`,
and [`ResizeObserver`](https://github.com/que-etc/resize-observer-polyfill) as
necessary for your desired browser support.
## Adding to a new web app

View File

@@ -132,13 +132,13 @@ const TextScreen = () => (
<DocItem
name="onLayout"
typeInfo="?function"
description={
description={[
<AppText>
Invoked on mount and layout changes with{' '}
<Code>{'{ nativeEvent: { layout: { x, y, width, height } } }'}</Code>, where{' '}
<Code>x</Code> and <Code>y</Code> are the offsets from the parent node.
</AppText>
}
]}
/>
<DocItem

View File

@@ -4,6 +4,7 @@
* @flow
*/
import PropOnLayout from './examples/PropOnLayout';
import PropPointerEvents from './examples/PropPointerEvents';
import transformExamples from './examples/transforms';
import ZIndexExample from './examples/ZIndex';
@@ -132,6 +133,9 @@ const ViewScreen = () => (
<Code>x</Code> and <Code>y</Code> are the offsets from the parent node.
</AppText>
}
example={{
render: () => <PropOnLayout />
}}
/>
<DocItem

View File

@@ -0,0 +1,76 @@
/**
* @flow
*/
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const l1 = { width: '100%', paddingLeft: 0, paddingTop: 0 };
const l2 = { width: '75%', paddingLeft: 10, paddingTop: 10 };
export default class ViewOnLayoutExample extends React.Component {
state = {
layoutInfo: {}
};
componentDidMount() {
this._layout = l1;
this._interval = setInterval(() => {
if (this._ref) {
this._ref.setNativeProps({ style: this._layout });
this._layout = this._layout.width === '100%' ? l2 : l1;
}
}, 2000);
}
componentWillUnmount() {
clearInterval(this._interval);
}
render() {
const { x, y, width, height } = this.state.layoutInfo;
return (
<View style={styles.root}>
<View style={styles.left}>
<Text>x: {x}</Text>
<Text>y: {y}</Text>
<Text>width: {width}</Text>
<Text>height: {height}</Text>
</View>
<View style={styles.right}>
<View ref={this._setRef} style={styles.container}>
<View onLayout={this._handleLayout} style={styles.box} />
</View>
</View>
</View>
);
}
_handleLayout = ({ nativeEvent }) => {
this.setState(() => ({ layoutInfo: nativeEvent.layout }));
};
_setRef = component => {
this._ref = component;
};
}
const styles = StyleSheet.create({
root: {
flexDirection: 'row'
},
container: {
height: 50
},
left: {
width: 100
},
right: {
flex: 1
},
box: {
backgroundColor: '#eee',
flex: 1
}
});

View File

@@ -18,7 +18,7 @@ const Box = ({ pointerEvents }) => (
</TouchableHighlight>
);
const ViewStyleExample = () => (
const ViewPointerEventsExample = () => (
<View pointerEvents="box-none">
<View pointerEvents="box-none" style={styles.container}>
<Box pointerEvents="none" />
@@ -43,4 +43,4 @@ const styles = StyleSheet.create({
borderStyle: 'solid'
}
});
export default ViewStyleExample;
export default ViewPointerEventsExample;