No Description

index.ios.js 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 { needChangeSource, 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. this.needChangeSource = needChangeSource(nextProps, this.props);
  62. }
  63. handleNavigationStateChange = navState => {
  64. const { title } = navState;
  65. if (!title) {
  66. return;
  67. }
  68. const [heightValue, widthValue] = title.split(',');
  69. const width = Number(widthValue);
  70. const height = Number(heightValue);
  71. const { height: oldHeight, width: oldWidth } = this.state;
  72. if ((height && height !== oldHeight) || (width && width !== oldWidth)) {
  73. // if ((height && height !== oldHeight)) {
  74. const { enableAnimation, animationDuration, onSizeUpdated } = this.props;
  75. enableAnimation && this.opacityAnimatedValue.setValue(0);
  76. this.setState(
  77. {
  78. height,
  79. width
  80. },
  81. () => {
  82. enableAnimation
  83. ? Animated.timing(this.opacityAnimatedValue, {
  84. toValue: 1,
  85. duration: animationDuration
  86. }).start(() => handleSizeUpdated(height, width, onSizeUpdated))
  87. : handleSizeUpdated(height, width, onSizeUpdated);
  88. }
  89. );
  90. }
  91. };
  92. getWebView = webView => (this.webView = webView);
  93. stopLoading() {
  94. this.webView.stopLoading();
  95. }
  96. render() {
  97. const { height, width, script } = this.state;
  98. const {
  99. onError,
  100. onLoad,
  101. onLoadStart,
  102. onLoadEnd,
  103. onShouldStartLoadWithRequest,
  104. scalesPageToFit,
  105. enableAnimation,
  106. source,
  107. heightOffset,
  108. style,
  109. scrollEnabled
  110. } = this.props;
  111. let webViewSource = Object.assign({}, source, { baseUrl: 'web/' });
  112. if (this.needChangeSource) {
  113. this.changeSourceFlag = !this.changeSourceFlag;
  114. webViewSource = Object.assign(webViewSource, { changeSourceFlag: this.changeSourceFlag });
  115. }
  116. return (
  117. <Animated.View
  118. style={[
  119. styles.container,
  120. {
  121. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  122. width,
  123. height: height + heightOffset
  124. },
  125. style
  126. ]}
  127. >
  128. <WebView
  129. originWhitelist={['*']}
  130. ref={this.getWebView}
  131. onError={onError}
  132. onLoad={onLoad}
  133. onLoadStart={onLoadStart}
  134. onLoadEnd={onLoadEnd}
  135. onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
  136. style={styles.webView}
  137. injectedJavaScript={script}
  138. scrollEnabled={!!scrollEnabled}
  139. scalesPageToFit={scalesPageToFit}
  140. source={webViewSource}
  141. onNavigationStateChange={this.handleNavigationStateChange}
  142. />
  143. </Animated.View>
  144. );
  145. }
  146. }
  147. const styles = StyleSheet.create({
  148. container: {
  149. backgroundColor: 'transparent'
  150. },
  151. webView: {
  152. flex: 1,
  153. backgroundColor: 'transparent'
  154. }
  155. });
  156. const commonScript = `
  157. updateSize();
  158. window.addEventListener('load', updateSize);
  159. window.addEventListener('resize', updateSize);
  160. `;
  161. const getSize = `
  162. function getSize(container) {
  163. var height = container.clientHeight || document.body.offsetHeight;
  164. var width = container.clientWidth || document.body.offsetWidth;
  165. return {
  166. height,
  167. width
  168. };
  169. }
  170. `;
  171. function getBaseScript(style) {
  172. return `
  173. ;
  174. ${getSize}
  175. (function () {
  176. var height = 0;
  177. var width = ${getWidth(style)};
  178. var wrapper = document.createElement('div');
  179. wrapper.id = 'rnahw-wrapper';
  180. while (document.body.firstChild instanceof Node) {
  181. wrapper.appendChild(document.body.firstChild);
  182. }
  183. document.body.appendChild(wrapper);
  184. function updateSize() {
  185. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  186. var size = getSize(wrapper);
  187. height = size.height;
  188. width = size.width;
  189. document.title = height.toString() + ',' + width.toString();
  190. }
  191. }
  192. ${commonScript}
  193. ${domMutationObserveScript}
  194. } ());
  195. `;
  196. }
  197. function getIframeBaseScript(style) {
  198. return `
  199. ;
  200. ${getSize}
  201. (function () {
  202. var height = 0;
  203. var width = ${getWidth(style)};
  204. function updateSize() {
  205. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  206. var size = getSize(document.body.firstChild);
  207. height = size.height;
  208. width = size.width;
  209. document.title = height.toString() + ',' + width.toString();
  210. }
  211. }
  212. ${commonScript}
  213. ${domMutationObserveScript}
  214. } ());
  215. `;
  216. }