No Description

index.android.js 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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. View,
  13. ViewPropTypes,
  14. WebView
  15. } from "react-native";
  16. import PropTypes from "prop-types";
  17. import Immutable from "immutable";
  18. const RCTAutoHeightWebView = requireNativeComponent(
  19. "RCTAutoHeightWebView",
  20. AutoHeightWebView,
  21. { nativeOnly:
  22. {
  23. nativeOnly: {
  24. onLoadingStart: true,
  25. onLoadingError: true,
  26. onLoadingFinish: true,
  27. messagingEnabled: PropTypes.bool
  28. }
  29. }
  30. }
  31. );
  32. export default class AutoHeightWebView extends PureComponent {
  33. static propTypes = {
  34. source: WebView.propTypes.source,
  35. onHeightUpdated: PropTypes.func,
  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. // baseUrl not work in android 4.3 or below version
  46. enableBaseUrl: PropTypes.bool,
  47. style: ViewPropTypes.style,
  48. // rn WebView callback
  49. onError: PropTypes.func,
  50. onLoad: PropTypes.func,
  51. onLoadStart: PropTypes.func,
  52. onLoadEnd: PropTypes.func,
  53. // works if set enableBaseUrl to true; add web/files... to android/app/src/assets/
  54. files: PropTypes.arrayOf(
  55. PropTypes.shape({
  56. href: PropTypes.string,
  57. type: PropTypes.string,
  58. rel: PropTypes.string
  59. })
  60. )
  61. };
  62. static defaultProps = {
  63. scalesPageToFit: true,
  64. enableBaseUrl: false,
  65. enableAnimation: true,
  66. animationDuration: 555,
  67. heightOffset: 20
  68. };
  69. constructor(props) {
  70. super(props);
  71. this.onMessage = this.onMessage.bind(this);
  72. if (this.props.enableAnimation) {
  73. this.opacityAnimatedValue = new Animated.Value(0);
  74. }
  75. if (IsBelowKitKat) {
  76. this.listenWebViewBridgeMessage = this.listenWebViewBridgeMessage.bind(
  77. this
  78. );
  79. }
  80. let initialScript = props.files
  81. ? this.appendFilesToHead(props.files, BaseScript)
  82. : BaseScript;
  83. initialScript = props.customStyle
  84. ? this.appendStylesToHead(props.customStyle, initialScript)
  85. : initialScript;
  86. this.state = {
  87. isChangingSource: false,
  88. height: 0,
  89. heightOffset: 0,
  90. script: initialScript
  91. };
  92. }
  93. componentWillMount() {
  94. if (IsBelowKitKat) {
  95. DeviceEventEmitter.addListener(
  96. "webViewBridgeMessage",
  97. this.listenWebViewBridgeMessage
  98. );
  99. }
  100. }
  101. componentDidMount() {
  102. this.startInterval();
  103. }
  104. componentWillReceiveProps(nextProps) {
  105. // injectedJavaScript only works when webview reload (source changed)
  106. if (
  107. Immutable.is(
  108. Immutable.fromJS(this.props.source),
  109. Immutable.fromJS(nextProps.source)
  110. )
  111. ) {
  112. return;
  113. } else {
  114. this.setState(
  115. {
  116. isChangingSource: true,
  117. height: 0,
  118. heightOffset: 0
  119. },
  120. () => {
  121. this.startInterval();
  122. this.setState({ isChangingSource: false });
  123. }
  124. );
  125. }
  126. let currentScript = BaseScript;
  127. if (nextProps.files) {
  128. currentScript = this.appendFilesToHead(nextProps.files, BaseScript);
  129. }
  130. currentScript = nextProps.customStyle
  131. ? this.appendStylesToHead(nextProps.customStyle, currentScript)
  132. : currentScript;
  133. this.setState({ script: currentScript });
  134. }
  135. componentWillUnmount() {
  136. this.stopInterval();
  137. if (IsBelowKitKat) {
  138. DeviceEventEmitter.removeListener(
  139. "webViewBridgeMessage",
  140. this.listenWebViewBridgeMessage
  141. );
  142. }
  143. }
  144. // below kitkat
  145. listenWebViewBridgeMessage(body) {
  146. this.onMessage(body.message);
  147. }
  148. // below kitkat
  149. sendToWebView(message) {
  150. UIManager.dispatchViewManagerCommand(
  151. findNodeHandle(this.webview),
  152. UIManager.RCTAutoHeightWebView.Commands.sendToWebView,
  153. [String(message)]
  154. );
  155. }
  156. postMessage(data) {
  157. UIManager.dispatchViewManagerCommand(
  158. findNodeHandle(this.webview),
  159. UIManager.RCTAutoHeightWebView.Commands.postMessage,
  160. [String(data)]
  161. );
  162. }
  163. startInterval() {
  164. this.finishInterval = false;
  165. this.interval = setInterval(() => {
  166. if (!this.finishInterval) {
  167. IsBelowKitKat
  168. ? this.sendToWebView("getBodyHeight")
  169. : this.postMessage("getBodyHeight");
  170. }
  171. }, 205);
  172. }
  173. stopInterval() {
  174. this.finishInterval = true;
  175. clearInterval(this.interval);
  176. }
  177. onHeightUpdated(height) {
  178. if (this.props.onHeightUpdated) {
  179. this.props.onHeightUpdated(height);
  180. }
  181. }
  182. onMessage(e) {
  183. const height = parseInt(
  184. IsBelowKitKat ? e.nativeEvent.message : e.nativeEvent.data
  185. );
  186. if (height) {
  187. if (this.props.enableAnimation) {
  188. this.opacityAnimatedValue.setValue(0);
  189. }
  190. this.stopInterval();
  191. this.setState(
  192. {
  193. heightOffset: this.props.heightOffset,
  194. height
  195. },
  196. () => {
  197. if (this.props.enableAnimation) {
  198. Animated.timing(this.opacityAnimatedValue, {
  199. toValue: 1,
  200. duration: this.props.animationDuration
  201. }).start(() => this.onHeightUpdated(height));
  202. } else {
  203. this.onHeightUpdated(height);
  204. }
  205. }
  206. );
  207. }
  208. }
  209. appendFilesToHead(files, script) {
  210. if (!files) {
  211. return script;
  212. }
  213. return files.reduceRight((file, combinedScript) => `
  214. var link = document.createElement('link');
  215. link.rel = '${file.rel}';
  216. link.type = '${file.type}';
  217. link.href = '${file.href}';
  218. document.head.appendChild(link);
  219. ${combinedScript}
  220. `, script);
  221. }
  222. appendStylesToHead(styles, script) {
  223. if (!styles) {
  224. return script;
  225. }
  226. // Escape any single quotes or newlines in the CSS with .replace()
  227. const escaped = styles.replace(/\'/g, "\\'").replace(/\n/g, '\\n')
  228. return `
  229. var styleElement = document.createElement('style');
  230. var styleText = document.createTextNode('${escaped}');
  231. styleElement.appendChild(styleText);
  232. document.head.appendChild(styleElement);
  233. ${script}
  234. `;
  235. }
  236. onLoadingStart = (event) => {
  237. var onLoadStart = this.props.onLoadStart;
  238. onLoadStart && onLoadStart(event);
  239. };
  240. onLoadingError = (event) => {
  241. var {onError, onLoadEnd} = this.props;
  242. onError && onError(event);
  243. onLoadEnd && onLoadEnd(event);
  244. console.warn('Encountered an error loading page', event.nativeEvent);
  245. };
  246. onLoadingFinish = (event) => {
  247. var {onLoad, onLoadEnd} = this.props;
  248. onLoad && onLoad(event);
  249. onLoadEnd && onLoadEnd(event);
  250. };
  251. stopLoading() {
  252. UIManager.dispatchViewManagerCommand(
  253. findNodeHandle(this.webview),
  254. UIManager.RCTAutoHeightWebView.Commands.stopLoading,
  255. null
  256. );
  257. }
  258. render() {
  259. const { height, script, isChangingSource, heightOffset } = this.state;
  260. const {
  261. scalesPageToFit,
  262. enableAnimation,
  263. source,
  264. customScript,
  265. style,
  266. enableBaseUrl
  267. } = this.props;
  268. let webViewSource = source;
  269. if (enableBaseUrl) {
  270. webViewSource = Object.assign({}, source, {
  271. baseUrl: "file:///android_asset/web/"
  272. });
  273. }
  274. return (
  275. <Animated.View
  276. style={[
  277. Styles.container,
  278. {
  279. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  280. height: height + heightOffset
  281. },
  282. style
  283. ]}
  284. >
  285. {isChangingSource ? null : (
  286. <RCTAutoHeightWebView
  287. onLoadingStart={this.onLoadingStart}
  288. onLoadingFinish={this.onLoadingFinish}
  289. onLoadingError={this.onLoadingError}
  290. ref={webview => (this.webview = webview)}
  291. style={Styles.webView}
  292. javaScriptEnabled={true}
  293. injectedJavaScript={script + customScript}
  294. scalesPageToFit={scalesPageToFit}
  295. source={webViewSource}
  296. onMessage={this.onMessage}
  297. messagingEnabled={true}
  298. // below kitkat
  299. onChange={this.onMessage}
  300. />
  301. )}
  302. </Animated.View>
  303. );
  304. }
  305. }
  306. const ScreenWidth = Dimensions.get("window").width;
  307. const IsBelowKitKat = Platform.Version < 19;
  308. const Styles = StyleSheet.create({
  309. container: {
  310. width: ScreenWidth,
  311. backgroundColor: "transparent"
  312. },
  313. webView: {
  314. flex: 1,
  315. backgroundColor: "transparent"
  316. }
  317. });
  318. const BaseScript = IsBelowKitKat
  319. ? `
  320. ; (function () {
  321. AutoHeightWebView.onMessage = function (message) {
  322. AutoHeightWebView.send(String(document.body.offsetHeight));
  323. };
  324. MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  325. var observer = new MutationObserver(function() {
  326. AutoHeightWebView.send(String(document.body.offsetHeight));
  327. });
  328. observer.observe(document, {
  329. subtree: true,
  330. attributes: true
  331. });
  332. } ());
  333. `
  334. : `
  335. ; (function () {
  336. document.addEventListener('message', function (e) {
  337. window.postMessage(String(document.body.offsetHeight));
  338. });
  339. MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  340. var observer = new MutationObserver(function() {
  341. window.postMessage(String(document.body.offsetHeight));
  342. });
  343. observer.observe(document, {
  344. subtree: true,
  345. attributes: true
  346. });
  347. } ());
  348. `;