No Description

index.ios.js 6.5KB

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