No Description

index.android.js 8.5KB

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