No Description

index.ios.js 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. 'use strict';
  2. import React, { PureComponent } from 'react';
  3. import { Animated, StyleSheet, ViewPropTypes, WebView } from 'react-native';
  4. import PropTypes from 'prop-types';
  5. import {
  6. isEqual,
  7. setState,
  8. getWidth,
  9. isSizeChanged,
  10. handleSizeUpdated,
  11. domMutationObserveScript,
  12. getCurrentSize
  13. } from './common.js';
  14. import momoize from './momoize';
  15. export default class AutoHeightWebView extends PureComponent {
  16. static propTypes = {
  17. hasIframe: PropTypes.bool,
  18. onNavigationStateChange: PropTypes.func,
  19. onMessage: PropTypes.func,
  20. source: WebView.propTypes.source,
  21. customScript: PropTypes.string,
  22. customStyle: PropTypes.string,
  23. enableAnimation: PropTypes.bool,
  24. style: ViewPropTypes.style,
  25. scrollEnabled: PropTypes.bool,
  26. // either height or width updated will trigger this
  27. onSizeUpdated: PropTypes.func,
  28. // if set to true may cause some layout issues (smaller font size)
  29. scalesPageToFit: PropTypes.bool,
  30. // only works on enable animation
  31. animationDuration: PropTypes.number,
  32. // offset of rn webview margin
  33. heightOffset: PropTypes.number,
  34. // rn WebView callback
  35. onError: PropTypes.func,
  36. onLoad: PropTypes.func,
  37. onLoadStart: PropTypes.func,
  38. onLoadEnd: PropTypes.func,
  39. onShouldStartLoadWithRequest: PropTypes.func,
  40. // add web/files... to project root
  41. files: PropTypes.arrayOf(
  42. PropTypes.shape({
  43. href: PropTypes.string,
  44. type: PropTypes.string,
  45. rel: PropTypes.string
  46. })
  47. )
  48. };
  49. static defaultProps = {
  50. scalesPageToFit: false,
  51. enableAnimation: true,
  52. animationDuration: 255,
  53. heightOffset: 12
  54. };
  55. constructor(props) {
  56. super(props);
  57. const { enableAnimation, style } = props;
  58. enableAnimation && (this.opacityAnimatedValue = new Animated.Value(0));
  59. this.webView = React.createRef();
  60. this.state = {
  61. isSizeChanged: false,
  62. width: getWidth(style),
  63. height: style && style.height ? style.height : 0
  64. };
  65. }
  66. getUpdatedState = momoize(setState, isEqual);
  67. static getDerivedStateFromProps(props, state) {
  68. const { height: oldHeight, width: oldWidth } = state;
  69. const height = props.style ? props.style.height : null;
  70. const width = props.style ? props.style.width : null;
  71. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  72. return {
  73. height,
  74. width,
  75. isSizeChanged: true
  76. };
  77. }
  78. return null;
  79. }
  80. componentDidUpdate() {
  81. const { height, width, isSizeChanged } = this.state;
  82. if (isSizeChanged) {
  83. const { enableAnimation, animationDuration, onSizeUpdated } = this.props;
  84. if (enableAnimation) {
  85. Animated.timing(this.opacityAnimatedValue, {
  86. toValue: 1,
  87. duration: animationDuration
  88. }).start(() => {
  89. handleSizeUpdated(height, width, onSizeUpdated);
  90. });
  91. } else {
  92. handleSizeUpdated(height, width, onSizeUpdated);
  93. }
  94. this.setState({ isSizeChanged: false });
  95. }
  96. }
  97. handleNavigationStateChange = navState => {
  98. const { title } = navState;
  99. const { onNavigationStateChange } = this.props;
  100. if (!title) {
  101. onNavigationStateChange && onNavigationStateChange();
  102. return;
  103. }
  104. const [heightValue, widthValue] = title.split(',');
  105. const width = Number(widthValue);
  106. const height = Number(heightValue);
  107. const { height: oldHeight, width: oldWidth } = this.state;
  108. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  109. this.props.enableAnimation && this.opacityAnimatedValue.setValue(0);
  110. this.setState({
  111. isSizeChanged: true,
  112. height,
  113. width
  114. });
  115. }
  116. onNavigationStateChange && onNavigationStateChange();
  117. };
  118. stopLoading() {
  119. this.webView.stopLoading();
  120. }
  121. render() {
  122. const { height, width } = this.state;
  123. const {
  124. onMessage,
  125. onError,
  126. onLoad,
  127. onLoadStart,
  128. onLoadEnd,
  129. onShouldStartLoadWithRequest,
  130. scalesPageToFit,
  131. enableAnimation,
  132. heightOffset,
  133. style,
  134. scrollEnabled
  135. } = this.props;
  136. const { source, script } = this.getUpdatedState(this.props, 'web/', getBaseScript, getIframeBaseScript);
  137. return (
  138. <Animated.View
  139. style={[
  140. styles.container,
  141. {
  142. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  143. width,
  144. height: height + heightOffset
  145. },
  146. style
  147. ]}
  148. >
  149. <WebView
  150. originWhitelist={['*']}
  151. ref={this.webView}
  152. onMessage={onMessage}
  153. onError={onError}
  154. onLoad={onLoad}
  155. onLoadStart={onLoadStart}
  156. onLoadEnd={onLoadEnd}
  157. onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
  158. style={styles.webView}
  159. scrollEnabled={!!scrollEnabled}
  160. scalesPageToFit={scalesPageToFit}
  161. injectedJavaScript={script}
  162. source={source}
  163. onNavigationStateChange={this.handleNavigationStateChange}
  164. />
  165. </Animated.View>
  166. );
  167. }
  168. }
  169. const styles = StyleSheet.create({
  170. container: {
  171. backgroundColor: 'transparent'
  172. },
  173. webView: {
  174. flex: 1,
  175. backgroundColor: 'transparent'
  176. }
  177. });
  178. const commonScript = `
  179. updateSize();
  180. window.addEventListener('load', updateSize);
  181. window.addEventListener('resize', updateSize);
  182. `;
  183. function getBaseScript(style) {
  184. return `
  185. ;
  186. ${getCurrentSize}
  187. (function () {
  188. var height = 0;
  189. var width = ${getWidth(style)};
  190. var wrapper = document.createElement('div');
  191. wrapper.id = 'rnahw-wrapper';
  192. while (document.body.firstChild instanceof Node) {
  193. wrapper.appendChild(document.body.firstChild);
  194. }
  195. document.body.appendChild(wrapper);
  196. function updateSize() {
  197. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  198. var size = getSize(wrapper);
  199. height = size.height;
  200. width = size.width;
  201. document.title = height.toString() + ',' + width.toString();
  202. }
  203. }
  204. ${commonScript}
  205. ${domMutationObserveScript}
  206. } ());
  207. `;
  208. }
  209. function getIframeBaseScript(style) {
  210. return `
  211. ;
  212. ${getCurrentSize}
  213. (function () {
  214. var height = 0;
  215. var width = ${getWidth(style)};
  216. function updateSize() {
  217. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  218. var size = getSize(document.body.firstChild);
  219. height = size.height;
  220. width = size.width;
  221. document.title = height.toString() + ',' + width.toString();
  222. }
  223. }
  224. ${commonScript}
  225. ${domMutationObserveScript}
  226. } ());
  227. `;
  228. }