Nessuna descrizione

index.android.js 9.7KB

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