Geen omschrijving

index.android.js 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. 'use strict';
  2. import React, { PureComponent } from 'react';
  3. import {
  4. findNodeHandle,
  5. requireNativeComponent,
  6. Animated,
  7. DeviceEventEmitter,
  8. Easing,
  9. StyleSheet,
  10. Platform,
  11. UIManager,
  12. ViewPropTypes,
  13. WebView
  14. } from 'react-native';
  15. import PropTypes from 'prop-types';
  16. import {
  17. isEqual,
  18. setState,
  19. isSizeChanged,
  20. handleSizeUpdated,
  21. getWidth,
  22. getScript,
  23. domMutationObserveScript,
  24. getCurrentSize
  25. } from './common.js';
  26. const RCTAutoHeightWebView = requireNativeComponent('RCTAutoHeightWebView', AutoHeightWebView, {
  27. nativeOnly: {
  28. nativeOnly: {
  29. onLoadingStart: true,
  30. onLoadingError: true,
  31. onLoadingFinish: true,
  32. messagingEnabled: PropTypes.bool
  33. }
  34. }
  35. });
  36. import momoize from './momoize';
  37. const baseUrl = 'file:///android_asset/web/';
  38. const getUpdatedState = momoize(setState, isEqual);
  39. export default class AutoHeightWebView extends PureComponent {
  40. static propTypes = {
  41. onMessage: PropTypes.func,
  42. scrollEnabled: PropTypes.bool,
  43. source: WebView.propTypes.source,
  44. customScript: PropTypes.string,
  45. customStyle: PropTypes.string,
  46. enableAnimation: PropTypes.bool,
  47. // either height or width updated will trigger this
  48. onSizeUpdated: PropTypes.func,
  49. // if set to false may cause some layout issues (width of container will be than width of screen)
  50. scalesPageToFit: PropTypes.bool,
  51. // only works on enable animation
  52. animationDuration: PropTypes.number,
  53. animationEasing: PropTypes.func,
  54. // offset of rn webView margin
  55. heightOffset: PropTypes.number,
  56. // baseUrl not work in android 4.3 or below version
  57. enableBaseUrl: PropTypes.bool,
  58. style: ViewPropTypes.style,
  59. // rn WebView callback
  60. onError: PropTypes.func,
  61. onLoad: PropTypes.func,
  62. onLoadStart: PropTypes.func,
  63. onLoadEnd: PropTypes.func,
  64. // works if set enableBaseUrl to true; add web/files... to android/app/src/assets/
  65. files: PropTypes.arrayOf(
  66. PropTypes.shape({
  67. href: PropTypes.string,
  68. type: PropTypes.string,
  69. rel: PropTypes.string
  70. })
  71. )
  72. };
  73. static defaultProps = {
  74. scalesPageToFit: true,
  75. enableBaseUrl: false,
  76. enableAnimation: true,
  77. animationDuration: 255,
  78. heightOffset: 20,
  79. animationEasing: Easing.out(Easing.quad)
  80. };
  81. constructor(props) {
  82. super(props);
  83. const { enableAnimation, style, source, enableBaseUrl, heightOffset } = props;
  84. isBelowKitKat && DeviceEventEmitter.addListener('webViewBridgeMessage', this.listenWebViewBridgeMessage);
  85. this.finishInterval = true;
  86. const initWidth = getWidth(style);
  87. const height = style ? (style.height ? style.height : 0) : 0;
  88. let state = {
  89. isSizeChanged: false,
  90. isSizeMayChange: false,
  91. height: height,
  92. width: initWidth,
  93. script: getScript(props, getBaseScript),
  94. source: enableBaseUrl ? Object.assign({}, source, { baseUrl }) : source
  95. };
  96. if (enableAnimation) {
  97. Object.assign(state, {
  98. heightValue: new Animated.Value(height + heightOffset),
  99. widthValue: new Animated.Value(initWidth)
  100. });
  101. }
  102. this.state = state;
  103. }
  104. componentDidMount() {
  105. this.startInterval();
  106. }
  107. static getDerivedStateFromProps(props, state) {
  108. const { height: oldHeight, width: oldWidth, source: prevSource, script: prevScript } = state;
  109. const { style, enableBaseUrl } = props;
  110. const { source, script } = getUpdatedState(props, enableBaseUrl ? baseUrl : null, getBaseScript);
  111. const height = style ? style.height : null;
  112. const width = style ? style.width : null;
  113. if (source.html !== prevSource.html || source.uri !== prevSource.uri || script !== prevScript) {
  114. return {
  115. source,
  116. script,
  117. isSizeMayChange: true
  118. };
  119. }
  120. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  121. return {
  122. height,
  123. width,
  124. isSizeChanged: true
  125. };
  126. }
  127. return null;
  128. }
  129. componentDidUpdate() {
  130. const { height, width, isSizeChanged, isSizeMayChange, heightValue, widthValue } = this.state;
  131. if (isSizeMayChange) {
  132. this.startInterval();
  133. this.setState({ isSizeMayChange: false });
  134. }
  135. if (isSizeChanged) {
  136. const { enableAnimation, animationDuration, animationEasing, onSizeUpdated, heightOffset } = this.props;
  137. if (enableAnimation) {
  138. Animated.parallel([
  139. Animated.timing(heightValue, {
  140. toValue: height + heightOffset,
  141. easing: animationEasing,
  142. duration: animationDuration
  143. }),
  144. Animated.timing(widthValue, {
  145. toValue: width,
  146. easing: animationEasing,
  147. duration: animationDuration
  148. })
  149. ]).start(() => {
  150. handleSizeUpdated(height, width, onSizeUpdated);
  151. });
  152. } else {
  153. handleSizeUpdated(height, width, onSizeUpdated);
  154. }
  155. this.setState({ isSizeChanged: false });
  156. }
  157. }
  158. componentWillUnmount() {
  159. this.stopInterval();
  160. isBelowKitKat && DeviceEventEmitter.removeListener('webViewBridgeMessage', this.listenWebViewBridgeMessage);
  161. }
  162. // below kitkat
  163. listenWebViewBridgeMessage = body => this.onMessage(body.message);
  164. // below kitkat
  165. sendToWebView(message) {
  166. UIManager.dispatchViewManagerCommand(
  167. findNodeHandle(this.webView),
  168. UIManager.RCTAutoHeightWebView.Commands.sendToWebView,
  169. [String(message)]
  170. );
  171. }
  172. postMessage(data) {
  173. UIManager.dispatchViewManagerCommand(
  174. findNodeHandle(this.webView),
  175. UIManager.RCTAutoHeightWebView.Commands.postMessage,
  176. [String(data)]
  177. );
  178. }
  179. startInterval() {
  180. if (this.finishInterval === false) {
  181. return;
  182. }
  183. this.finishInterval = false;
  184. this.interval = setInterval(() => {
  185. if (!this.finishInterval) {
  186. isBelowKitKat ? this.sendToWebView('getBodyHeight') : this.postMessage('getBodyHeight');
  187. }
  188. }, 205);
  189. }
  190. stopInterval() {
  191. this.finishInterval = true;
  192. clearInterval(this.interval);
  193. }
  194. onMessage = e => {
  195. if (!e.nativeEvent) {
  196. return;
  197. }
  198. const { height, width } = JSON.parse(isBelowKitKat ? e.nativeEvent.message : e.nativeEvent.data);
  199. const { height: oldHeight, width: oldWidth } = this.state;
  200. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  201. this.stopInterval();
  202. this.setState({
  203. isSizeChanged: true,
  204. height,
  205. width
  206. });
  207. }
  208. const { onMessage } = this.props;
  209. onMessage && onMessage(e);
  210. };
  211. onLoadingStart = event => {
  212. const { onLoadStart } = this.props;
  213. onLoadStart && onLoadStart(event);
  214. };
  215. onLoadingError = event => {
  216. const { onError, onLoadEnd } = this.props;
  217. onError && onError(event);
  218. onLoadEnd && onLoadEnd(event);
  219. console.warn('Encountered an error loading page', event.nativeEvent);
  220. };
  221. onLoadingFinish = event => {
  222. const { onLoad, onLoadEnd } = this.props;
  223. onLoad && onLoad(event);
  224. onLoadEnd && onLoadEnd(event);
  225. };
  226. stopLoading() {
  227. UIManager.dispatchViewManagerCommand(
  228. findNodeHandle(this.webView),
  229. UIManager.RCTAutoHeightWebView.Commands.stopLoading,
  230. null
  231. );
  232. }
  233. getWebView = webView => (this.webView = webView);
  234. render() {
  235. const { height, width, script, source, heightValue, widthValue } = this.state;
  236. const { scalesPageToFit, style, scrollEnabled, heightOffset, enableAnimation } = this.props;
  237. return (
  238. <Animated.View
  239. style={[
  240. styles.container,
  241. {
  242. height: enableAnimation ? heightValue : height ? height + heightOffset : 0,
  243. width: enableAnimation ? widthValue : width
  244. },
  245. style
  246. ]}
  247. >
  248. <RCTAutoHeightWebView
  249. onLoadingStart={this.onLoadingStart}
  250. onLoadingFinish={this.onLoadingFinish}
  251. onLoadingError={this.onLoadingError}
  252. originWhitelist={['.*']}
  253. ref={this.getWebView}
  254. style={styles.webView}
  255. javaScriptEnabled={true}
  256. injectedJavaScript={script}
  257. scalesPageToFit={scalesPageToFit}
  258. scrollEnabled={!!scrollEnabled}
  259. source={source}
  260. onMessage={this.onMessage}
  261. messagingEnabled={true}
  262. // below kitkat
  263. onChange={this.onMessage}
  264. />
  265. </Animated.View>
  266. );
  267. }
  268. }
  269. const isBelowKitKat = Platform.Version < 19;
  270. const styles = StyleSheet.create({
  271. container: {
  272. backgroundColor: 'transparent'
  273. },
  274. webView: {
  275. flex: 1,
  276. backgroundColor: 'transparent'
  277. }
  278. });
  279. const commonScript = `
  280. ${getCurrentSize}
  281. var wrapper = document.createElement("div");
  282. wrapper.id = "wrapper";
  283. while (document.body.firstChild instanceof Node) {
  284. wrapper.appendChild(document.body.firstChild);
  285. }
  286. document.body.appendChild(wrapper);
  287. var height = 0;
  288. `;
  289. const getBaseScript = isBelowKitKat
  290. ? function(style) {
  291. return `
  292. ;
  293. ${commonScript}
  294. var width = ${getWidth(style)};
  295. function updateSize() {
  296. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  297. var size = getSize(document.body.firstChild);
  298. height = size.height;
  299. width = size.width;
  300. AutoHeightWebView.send(JSON.stringify({ width, height }));
  301. }
  302. }
  303. (function () {
  304. AutoHeightWebView.onMessage = updateSize;
  305. ${domMutationObserveScript}
  306. } ());
  307. `;
  308. }
  309. : function(style) {
  310. return `
  311. ;
  312. ${commonScript}
  313. var width = ${getWidth(style)};
  314. function updateSize() {
  315. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  316. var size = getSize(document.body.firstChild);
  317. height = size.height;
  318. width = size.width;
  319. window.postMessage(JSON.stringify({ width, height }), '*');
  320. }
  321. }
  322. (function () {
  323. document.addEventListener("message", updateSize);
  324. ${domMutationObserveScript}
  325. } ());
  326. `;
  327. };