No Description

index.android.js 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use strict'
  2. import React, {
  3. Component,
  4. PropTypes
  5. } from 'react';
  6. import {
  7. findNodeHandle,
  8. requireNativeComponent,
  9. Dimensions,
  10. UIManager,
  11. View,
  12. WebView
  13. } from 'react-native';
  14. const RCTAutoHeightWebView = requireNativeComponent('RCTAutoHeightWebView', AutoHeightWebView, { nativeOnly: { messagingEnabled: PropTypes.bool } });
  15. export default class AutoHeightWebView extends Component {
  16. constructor(props) {
  17. super(props);
  18. this.onMessage = this.onMessage.bind(this);
  19. this.onLoadingStart = this.onLoadingStart.bind(this);
  20. const initialScript = props.files ? this.appendFilesToHead(props.files, BaseScript) : BaseScript;
  21. this.state = {
  22. isOnLoadingStart: false,
  23. height: 0,
  24. heightOffset: 0,
  25. script: initialScript
  26. };
  27. }
  28. componentDidMount() {
  29. this.startInterval();
  30. }
  31. componentWillReceiveProps(nextProps) {
  32. // injectedJavaScript only works when webview reload (html changed)
  33. if (nextProps.html === this.props.html) {
  34. this.htmlHasChanged = false;
  35. return;
  36. }
  37. else {
  38. this.htmlHasChanged = true;
  39. this.setState({
  40. height: 0,
  41. heightOffset: 0
  42. });
  43. }
  44. let currentScript = BaseScript;
  45. if ((nextProps.files && !this.props.files) || (nextProps.files && this.props.files && JSON.stringify(nextProps.files) !== JSON.stringify(this.props.files))) {
  46. currentScript = this.appendFilesToHead(nextProps.files, BaseScript);
  47. }
  48. this.setState({ script: currentScript });
  49. }
  50. componentDidUpdate(prevProps, prevState) {
  51. if (this.htmlHasChanged) {
  52. if (this.state.isOnLoadingStart && this.state.height === 0 && this.state.heightOffset === 0) {
  53. this.stopInterval = false;
  54. this.startInterval();
  55. this.htmlHasChanged = false;
  56. this.setState({ isOnLoadingStart: false });
  57. }
  58. }
  59. }
  60. componentWillUnmount() {
  61. this.stopInterval();
  62. }
  63. onLoadingStart() {
  64. if (this.htmlHasChanged) {
  65. this.setState({ isOnLoadingStart: true });
  66. }
  67. }
  68. postMessage(data) {
  69. UIManager.dispatchViewManagerCommand(
  70. findNodeHandle(this.webview),
  71. UIManager.RCTWebView.Commands.postMessage,
  72. [String(data)]
  73. );
  74. };
  75. startInterval() {
  76. this.stopInterval = false;
  77. this.interval = setInterval(() => {
  78. if (!this.stopInterval) {
  79. this.postMessage('getBodyHeight');
  80. }
  81. }, 205);
  82. }
  83. stopInterval() {
  84. this.stopInterval = true;
  85. clearInterval(this.interval);
  86. }
  87. onMessage(e) {
  88. const height = parseInt(e.nativeEvent.data);
  89. if (height) {
  90. this.stopInterval();
  91. this.setState({
  92. heightOffset: this.props.heightOffset,
  93. height
  94. });
  95. if (this.props.onHeightUpdated) {
  96. this.props.onHeightUpdated(height);
  97. }
  98. }
  99. }
  100. appendFilesToHead(files, script) {
  101. if (!files) {
  102. return script;
  103. }
  104. for (let file of files) {
  105. script =
  106. `
  107. var link = document.createElement('link');
  108. link.rel = '` + file.rel + `';
  109. link.type = '` + file.type + `';
  110. link.href = '` + file.href + `';
  111. document.head.appendChild(link);
  112. `+ script;
  113. }
  114. return script;
  115. }
  116. render() {
  117. const source = this.props.enableBaseUrl ? {
  118. html: this.props.html,
  119. baseUrl: 'file:///android_asset/web/'
  120. } : { html: this.props.html };
  121. return (
  122. <View style={[{
  123. width: ScreenWidth,
  124. height: this.state.height + this.state.heightOffset
  125. }, this.props.style]}>
  126. <RCTAutoHeightWebView
  127. ref={webview => this.webview = webview}
  128. style={{ flex: 1 }}
  129. javaScriptEnabled={true}
  130. injectedJavaScript={this.state.script + this.props.customScript}
  131. onLoadingStart={this.onLoadingStart}
  132. scrollEnabled={false}
  133. source={source}
  134. onMessage={this.onMessage}
  135. messagingEnabled={true} />
  136. </View>
  137. );
  138. }
  139. }
  140. AutoHeightWebView.propTypes = {
  141. ...WebView.propTypes,
  142. html: PropTypes.string,
  143. onHeightUpdated: PropTypes.func,
  144. customScript: PropTypes.string,
  145. // offset rn webview margin
  146. heightOffset: PropTypes.number,
  147. // baseUrl not work in android 4.3 or below version
  148. enableBaseUrl: PropTypes.bool,
  149. // works if set enableBaseUrl to true; add web/files... to android/app/src/assets/
  150. files: PropTypes.arrayOf(PropTypes.shape({
  151. href: PropTypes.string,
  152. type: PropTypes.string,
  153. rel: PropTypes.string
  154. }))
  155. }
  156. AutoHeightWebView.defaultProps = {
  157. enableBaseUrl: false,
  158. heightOffset: 20
  159. }
  160. const ScreenWidth = Dimensions.get('window').width;
  161. const BaseScript =
  162. `
  163. ; (function () {
  164. document.addEventListener('message', function (e) {
  165. window.postMessage(String(document.body.offsetHeight));
  166. });
  167. } ());
  168. `;