No Description

index.android.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. 'use strict';
  2. import React, { PureComponent } from 'react';
  3. import {
  4. findNodeHandle,
  5. requireNativeComponent,
  6. Animated,
  7. DeviceEventEmitter,
  8. Dimensions,
  9. StyleSheet,
  10. Platform,
  11. UIManager,
  12. ViewPropTypes,
  13. WebView
  14. } from 'react-native';
  15. import PropTypes from 'prop-types';
  16. import Immutable from 'immutable';
  17. import { getScript, onHeightUpdated, onWidthUpdated, onHeightWidthUpdated, domMutationObserveScript } from './common.js';
  18. const RCTAutoHeightWebView = requireNativeComponent('RCTAutoHeightWebView', AutoHeightWebView, {
  19. nativeOnly: {
  20. nativeOnly: {
  21. onLoadingStart: true,
  22. onLoadingError: true,
  23. onLoadingFinish: true,
  24. messagingEnabled: PropTypes.bool
  25. }
  26. }
  27. });
  28. const screenWidth = Dimensions.get('window').width;
  29. export default class AutoHeightWebView extends PureComponent {
  30. static propTypes = {
  31. source: WebView.propTypes.source,
  32. onHeightUpdated: PropTypes.func,
  33. onWidthUpdated: PropTypes.func,
  34. onHeightWidthUpdated: PropTypes.func,
  35. shouldResizeWidth: PropTypes.bool,
  36. customScript: PropTypes.string,
  37. customStyle: PropTypes.string,
  38. enableAnimation: PropTypes.bool,
  39. // if set to false may cause some layout issues (width of container will be than width of screen)
  40. scalesPageToFit: PropTypes.bool,
  41. // only works on enable animation
  42. animationDuration: PropTypes.number,
  43. // offset of rn webView margin
  44. heightOffset: PropTypes.number,
  45. widthOffset: PropTypes.number,
  46. // baseUrl not work in android 4.3 or below version
  47. enableBaseUrl: PropTypes.bool,
  48. style: ViewPropTypes.style,
  49. // rn WebView callback
  50. onError: PropTypes.func,
  51. onLoad: PropTypes.func,
  52. onLoadStart: PropTypes.func,
  53. onLoadEnd: PropTypes.func,
  54. // works if set enableBaseUrl to true; add web/files... to android/app/src/assets/
  55. files: PropTypes.arrayOf(
  56. PropTypes.shape({
  57. href: PropTypes.string,
  58. type: PropTypes.string,
  59. rel: PropTypes.string
  60. })
  61. )
  62. };
  63. static defaultProps = {
  64. scalesPageToFit: true,
  65. enableBaseUrl: false,
  66. enableAnimation: true,
  67. animationDuration: 555,
  68. heightOffset: 20,
  69. widthOffset: 20,
  70. shouldResizeWidth: false
  71. };
  72. constructor(props) {
  73. super(props);
  74. props.enableAnimation && (this.opacityAnimatedValue = new Animated.Value(0));
  75. isBelowKitKat && DeviceEventEmitter.addListener('webViewBridgeMessage', this.listenWebViewBridgeMessage);
  76. this.state = {
  77. isChangingSource: false,
  78. height: 0,
  79. heightOffset: 0,
  80. width: screenWidth,
  81. widthOffset: 0,
  82. script: getScript(props, baseScript)
  83. };
  84. }
  85. componentDidMount() {
  86. this.startInterval();
  87. }
  88. componentWillReceiveProps(nextProps) {
  89. // injectedJavaScript only works when webView reload (source changed)
  90. if (Immutable.is(Immutable.fromJS(this.props.source), Immutable.fromJS(nextProps.source))) {
  91. return;
  92. }
  93. else {
  94. this.setState(
  95. {
  96. isChangingSource: true,
  97. height: 0,
  98. heightOffset: 0,
  99. width: 0,
  100. widthOffset: 0,
  101. },
  102. () => {
  103. this.startInterval();
  104. this.setState({ isChangingSource: false });
  105. }
  106. );
  107. }
  108. this.setState({ script: getScript(nextProps, baseScript) });
  109. }
  110. componentWillUnmount() {
  111. this.stopInterval();
  112. isBelowKitKat && DeviceEventEmitter.removeListener('webViewBridgeMessage', this.listenWebViewBridgeMessage);
  113. }
  114. // below kitkat
  115. listenWebViewBridgeMessage = body => this.onMessage(body.message);
  116. // below kitkat
  117. sendToWebView(message) {
  118. UIManager.dispatchViewManagerCommand(
  119. findNodeHandle(this.webView),
  120. UIManager.RCTAutoHeightWebView.Commands.sendToWebView,
  121. [String(message)]
  122. );
  123. }
  124. postMessage(data) {
  125. UIManager.dispatchViewManagerCommand(
  126. findNodeHandle(this.webView),
  127. UIManager.RCTAutoHeightWebView.Commands.postMessage,
  128. [String(data)]
  129. );
  130. }
  131. startInterval() {
  132. this.finishInterval = false;
  133. this.interval = setInterval(() => {
  134. if (!this.finishInterval) {
  135. isBelowKitKat ? this.sendToWebView('getBodyHeight') : this.postMessage('getBodyHeight');
  136. }
  137. }, 205);
  138. }
  139. stopInterval() {
  140. this.finishInterval = true;
  141. clearInterval(this.interval);
  142. }
  143. onMessage = e => {
  144. const { height, width } = JSON.parse(isBelowKitKat ? e.nativeEvent.message : e.nativeEvent.data);
  145. if (height && height !== this.state.height && width && width !== this.state.width) {
  146. const { enableAnimation, animationDuration, heightOffset, widthOffset } = this.props;
  147. enableAnimation && this.opacityAnimatedValue.setValue(0);
  148. this.stopInterval();
  149. this.setState(
  150. {
  151. heightOffset,
  152. height,
  153. widthOffset,
  154. width
  155. },
  156. () => {
  157. enableAnimation
  158. ? Animated.timing(this.opacityAnimatedValue, {
  159. toValue: 1,
  160. duration: animationDuration
  161. }).start(() => onHeightWidthUpdated(height, width, this.props))
  162. : onHeightWidthUpdated(height, width, this.props);
  163. }
  164. );
  165. }
  166. };
  167. onLoadingStart = event => {
  168. const { onLoadStart } = this.props;
  169. onLoadStart && onLoadStart(event);
  170. };
  171. onLoadingError = event => {
  172. const { onError, onLoadEnd } = this.props;
  173. onError && onError(event);
  174. onLoadEnd && onLoadEnd(event);
  175. console.warn('Encountered an error loading page', event.nativeEvent);
  176. };
  177. onLoadingFinish = event => {
  178. const { onLoad, onLoadEnd } = this.props;
  179. onLoad && onLoad(event);
  180. onLoadEnd && onLoadEnd(event);
  181. };
  182. getWebView = webView => (this.webView = webView);
  183. stopLoading() {
  184. UIManager.dispatchViewManagerCommand(
  185. findNodeHandle(this.webView),
  186. UIManager.RCTAutoHeightWebView.Commands.stopLoading,
  187. null
  188. );
  189. }
  190. render() {
  191. const { height, width, script, isChangingSource, heightOffset, widthOffset } = this.state;
  192. const { scalesPageToFit, enableAnimation, source, customScript, style, enableBaseUrl } = this.props;
  193. let webViewSource = source;
  194. if (enableBaseUrl) {
  195. webViewSource = Object.assign({}, source, {
  196. baseUrl: 'file:///android_asset/web/'
  197. });
  198. }
  199. return (
  200. <Animated.View
  201. style={[
  202. styles.container,
  203. {
  204. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  205. height: height + heightOffset,
  206. width: width + widthOffset
  207. },
  208. style
  209. ]}
  210. >
  211. {isChangingSource ? null : (
  212. <RCTAutoHeightWebView
  213. onLoadingStart={this.onLoadingStart}
  214. onLoadingFinish={this.onLoadingFinish}
  215. onLoadingError={this.onLoadingError}
  216. ref={this.getWebView}
  217. style={styles.webView}
  218. javaScriptEnabled={true}
  219. injectedJavaScript={script + customScript}
  220. scalesPageToFit={scalesPageToFit}
  221. source={webViewSource}
  222. onMessage={this.onMessage}
  223. messagingEnabled={true}
  224. // below kitkat
  225. onChange={this.onMessage}
  226. />
  227. )}
  228. </Animated.View>
  229. );
  230. }
  231. }
  232. const isBelowKitKat = Platform.Version < 19;
  233. const styles = StyleSheet.create({
  234. container: {
  235. //width: screenWidth,
  236. backgroundColor: 'transparent'
  237. },
  238. webView: {
  239. flex: 1,
  240. backgroundColor: 'transparent'
  241. }
  242. });
  243. const baseScript = isBelowKitKat
  244. ? `
  245. ; (function () {
  246. var wrapper = document.createElement('div');
  247. wrapper.id = 'wrapper';
  248. while (document.body.firstChild instanceof Node) {
  249. wrapper.appendChild(document.body.firstChild);
  250. }
  251. document.body.appendChild(wrapper);
  252. AutoHeightWebView.onMessage = function (message) {
  253. var rect = document.body.firstElementChild.getBoundingClientRect().toJSON();
  254. var width = Math.round(rect.width);
  255. var height = Math.round(rect.height);
  256. AutoHeightWebView.send(JSON.stringify({ width, height }));
  257. };
  258. ${domMutationObserveScript}
  259. } ());
  260. `
  261. : `
  262. ; (function () {
  263. var wrapper = document.createElement('div');
  264. wrapper.id = 'wrapper';
  265. while (document.body.firstChild instanceof Node) {
  266. wrapper.appendChild(document.body.firstChild);
  267. }
  268. document.body.appendChild(wrapper);
  269. document.addEventListener('message', function (e) {
  270. var rect = document.body.firstElementChild.getBoundingClientRect().toJSON();
  271. var width = Math.round(rect.width);
  272. var height = Math.round(rect.height);
  273. window.postMessage(JSON.stringify({ width, height }));
  274. });
  275. ${domMutationObserveScript}
  276. } ());
  277. `;