Няма описание

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. 'use strict'
  2. import React, { PureComponent } from 'react';
  3. import {
  4. Animated,
  5. Dimensions,
  6. StyleSheet,
  7. View,
  8. ViewPropTypes,
  9. WebView
  10. } from 'react-native';
  11. import PropTypes from 'prop-types';
  12. export default class AutoHeightWebView extends PureComponent {
  13. static propTypes = {
  14. hasIframe: PropTypes.bool,
  15. source: WebView.propTypes.source,
  16. onHeightUpdated: PropTypes.func,
  17. customScript: PropTypes.string,
  18. customStyle: PropTypes.string,
  19. enableAnimation: PropTypes.bool,
  20. // if set to true may cause some layout issues (smaller font size)
  21. scalesPageToFit: PropTypes.bool,
  22. // only works on enable animation
  23. animationDuration: PropTypes.number,
  24. // offset of rn webview margin
  25. heightOffset: PropTypes.number,
  26. style: ViewPropTypes.style,
  27. // rn WebView callback
  28. onError: PropTypes.func,
  29. onLoad: PropTypes.func,
  30. onLoadStart: PropTypes.func,
  31. onLoadEnd: PropTypes.func,
  32. onShouldStartLoadWithRequest: PropTypes.func,
  33. _ref: PropTypes.func,
  34. // add web/files... to project root
  35. files: PropTypes.arrayOf(PropTypes.shape({
  36. href: PropTypes.string,
  37. type: PropTypes.string,
  38. rel: PropTypes.string
  39. }))
  40. }
  41. static defaultProps = {
  42. scalesPageToFit: false,
  43. enableAnimation: true,
  44. animationDuration: 555,
  45. heightOffset: 12
  46. }
  47. constructor(props) {
  48. super(props);
  49. this.handleNavigationStateChange = this.handleNavigationStateChange.bind(this);
  50. if (this.props.enableAnimation) {
  51. this.opacityAnimatedValue = new Animated.Value(0);
  52. }
  53. let initialScript = props.hasIframe ? IframeBaseScript : BaseScript;
  54. initialScript = props.files
  55. ? this.appendFilesToHead(props.files, BaseScript)
  56. : BaseScript;
  57. initialScript = props.customStyle
  58. ? this.appendStylesToHead(props.customStyle, initialScript)
  59. : initialScript;
  60. this.state = {
  61. height: 0,
  62. script: initialScript
  63. };
  64. }
  65. componentWillReceiveProps(nextProps) {
  66. let currentScript = nextProps.hasIframe ? IframeBaseScript : BaseScript;
  67. if (nextProps.files) {
  68. currentScript = this.appendFilesToHead(nextProps.files, currentScript);
  69. }
  70. currentScript = nextProps.customStyle
  71. ? this.appendStylesToHead(nextProps.customStyle, currentScript)
  72. : currentScript;
  73. this.setState({ script: currentScript });
  74. }
  75. appendFilesToHead(files, script) {
  76. if (!files) {
  77. return script;
  78. }
  79. return files.reduceRight((file, combinedScript) => `
  80. var link = document.createElement('link');
  81. link.rel = '${file.rel}';
  82. link.type = '${file.type}';
  83. link.href = '${file.href}';
  84. document.head.appendChild(link);
  85. ${combinedScript}
  86. `, script);
  87. }
  88. appendStylesToHead(styles, script) {
  89. if (!styles) {
  90. return script;
  91. }
  92. // Escape any single quotes or newlines in the CSS with .replace()
  93. const escaped = styles.replace(/\'/g, "\\'").replace(/\n/g, '\\n')
  94. return `
  95. var styleElement = document.createElement('style');
  96. var styleText = document.createTextNode('${escaped}');
  97. styleElement.appendChild(styleText);
  98. document.head.appendChild(styleElement);
  99. ${script}
  100. `;
  101. }
  102. onHeightUpdated(height) {
  103. if (this.props.onHeightUpdated) {
  104. this.props.onHeightUpdated(height);
  105. }
  106. }
  107. handleNavigationStateChange(navState) {
  108. const height = Number(navState.title);
  109. if (height && height !== this.state.height) {
  110. if (this.props.enableAnimation) {
  111. this.opacityAnimatedValue.setValue(0);
  112. }
  113. this.setState({ height }, () => {
  114. if (this.props.enableAnimation) {
  115. Animated.timing(this.opacityAnimatedValue, {
  116. toValue: 1,
  117. duration: this.props.animationDuration
  118. }).start(() => this.onHeightUpdated(height));
  119. }
  120. else {
  121. this.onHeightUpdated(height);
  122. }
  123. });
  124. }
  125. }
  126. render() {
  127. const { height, script } = this.state;
  128. const { _ref, onError, onLoad, onLoadStart, onLoadEnd, onShouldStartLoadWithRequest, scalesPageToFit, enableAnimation, source, heightOffset, customScript, style } = this.props;
  129. const webViewSource = Object.assign({}, source, { baseUrl: 'web/' });
  130. return (
  131. <Animated.View style={[Styles.container, {
  132. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  133. height: height + heightOffset,
  134. }, style]}>
  135. <WebView
  136. ref={_ref}
  137. onError={onError}
  138. onLoad={onLoad}
  139. onLoadStart={onLoadStart}
  140. onLoadEnd={onLoadEnd}
  141. onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
  142. style={Styles.webView}
  143. injectedJavaScript={script + customScript}
  144. scrollEnabled={false}
  145. scalesPageToFit={scalesPageToFit}
  146. source={webViewSource}
  147. onNavigationStateChange={this.handleNavigationStateChange} />
  148. </Animated.View>
  149. );
  150. }
  151. }
  152. const ScreenWidth = Dimensions.get('window').width;
  153. const Styles = StyleSheet.create({
  154. container: {
  155. width: ScreenWidth,
  156. backgroundColor: 'transparent'
  157. },
  158. webView: {
  159. flex: 1,
  160. backgroundColor: 'transparent'
  161. }
  162. });
  163. const BaseScript =
  164. `
  165. ;
  166. (function () {
  167. var i = 0;
  168. var height = 0;
  169. var wrapper = document.createElement('div');
  170. wrapper.id = 'height-wrapper';
  171. while (document.body.firstChild) {
  172. wrapper.appendChild(document.body.firstChild);
  173. }
  174. document.body.appendChild(wrapper);
  175. function updateHeight() {
  176. if(document.body.offsetHeight !== height) {
  177. height = wrapper.clientHeight;
  178. document.title = wrapper.clientHeight;
  179. window.location.hash = ++i;
  180. }
  181. }
  182. updateHeight();
  183. window.addEventListener('load', updateHeight);
  184. window.addEventListener('resize', updateHeight);
  185. } ());
  186. `;
  187. const IframeBaseScript =
  188. `
  189. ;
  190. (function () {
  191. var i = 0;
  192. var height = 0;
  193. function updateHeight() {
  194. if(document.body.offsetHeight !== height) {
  195. height = document.body.firstChild.clientHeight;
  196. document.title = document.body.firstChild.clientHeight;
  197. window.location.hash = ++i;
  198. }
  199. }
  200. updateHeight();
  201. window.addEventListener('load', updateHeight);
  202. window.addEventListener('resize', updateHeight);
  203. } ());
  204. `;