No Description

index.ios.js 6.5KB

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