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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. 'use strict';
  2. import React, { PureComponent } from 'react';
  3. import { Animated, StyleSheet } from 'react-native';
  4. import PropTypes from 'prop-types';
  5. import { commonPropTypes } from './propTypes.js';
  6. import { WebView } from 'react-native-webview';
  7. import {
  8. isEqual,
  9. setState,
  10. getWidth,
  11. isSizeChanged,
  12. handleSizeUpdated,
  13. domMutationObserveScript,
  14. getCurrentSize
  15. } from './common.js';
  16. import momoize from './momoize';
  17. export default class AutoHeightWebView extends PureComponent {
  18. static propTypes = {
  19. ...commonPropTypes,
  20. hasIframe: PropTypes.bool,
  21. // only works on enable animation
  22. animationDuration: PropTypes.number,
  23. // offset of rn webview margin
  24. heightOffset: PropTypes.number,
  25. // webview props
  26. scrollEnabled: PropTypes.bool,
  27. onShouldStartLoadWithRequest: PropTypes.func,
  28. decelerationRate: PropTypes.number,
  29. allowsInlineMediaPlayback: PropTypes.bool,
  30. bounces: PropTypes.bool,
  31. dataDetectorTypes: PropTypes.oneOfType([PropTypes.string, PropTypes.array])
  32. };
  33. static defaultProps = {
  34. baseUrl: 'web/',
  35. enableAnimation: true,
  36. animationDuration: 255,
  37. heightOffset: 12
  38. };
  39. constructor(props) {
  40. super(props);
  41. const { enableAnimation, style } = props;
  42. enableAnimation && (this.opacityAnimatedValue = new Animated.Value(0));
  43. this.webView = React.createRef();
  44. this.state = {
  45. isSizeChanged: false,
  46. width: getWidth(style),
  47. height: style && style.height ? style.height : 0
  48. };
  49. }
  50. getUpdatedState = momoize(setState, isEqual);
  51. static getDerivedStateFromProps(props, state) {
  52. const { height: oldHeight, width: oldWidth } = state;
  53. const height = props.style ? props.style.height : null;
  54. const width = props.style ? props.style.width : null;
  55. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  56. return {
  57. height: height || oldHeight,
  58. width: width || oldWidth,
  59. isSizeChanged: true
  60. };
  61. }
  62. return null;
  63. }
  64. componentDidUpdate() {
  65. const { height, width, isSizeChanged } = this.state;
  66. if (isSizeChanged) {
  67. const { enableAnimation, animationDuration, onSizeUpdated } = this.props;
  68. if (enableAnimation) {
  69. Animated.timing(this.opacityAnimatedValue, {
  70. toValue: 1,
  71. duration: animationDuration
  72. }).start(() => {
  73. handleSizeUpdated(height, width, onSizeUpdated);
  74. });
  75. } else {
  76. handleSizeUpdated(height, width, onSizeUpdated);
  77. }
  78. this.setState({ isSizeChanged: false });
  79. }
  80. }
  81. handleNavigationStateChange = navState => {
  82. const { title } = navState;
  83. const { onNavigationStateChange } = this.props;
  84. if (!title) {
  85. onNavigationStateChange && onNavigationStateChange(navState);
  86. return;
  87. }
  88. const [heightValue, widthValue] = title.split(',');
  89. const width = Number(widthValue);
  90. const height = Number(heightValue);
  91. const { height: oldHeight, width: oldWidth } = this.state;
  92. if (isSizeChanged(height, oldHeight, width, oldWidth)) {
  93. this.props.enableAnimation && this.opacityAnimatedValue.setValue(0);
  94. this.setState({
  95. isSizeChanged: true,
  96. height,
  97. width
  98. });
  99. }
  100. onNavigationStateChange && onNavigationStateChange(navState);
  101. };
  102. stopLoading() {
  103. this.webView.current.stopLoading();
  104. }
  105. render() {
  106. const { height, width } = this.state;
  107. const {
  108. renderError,
  109. originWhitelist,
  110. mediaPlaybackRequiresUserAction,
  111. bounces,
  112. decelerationRate,
  113. allowsInlineMediaPlayback,
  114. dataDetectorTypes,
  115. onMessage,
  116. onError,
  117. onLoad,
  118. onLoadStart,
  119. onLoadEnd,
  120. onShouldStartLoadWithRequest,
  121. enableAnimation,
  122. heightOffset,
  123. style,
  124. scrollEnabled
  125. } = this.props;
  126. const { source, script } = this.getUpdatedState(this.props, getBaseScript, getIframeBaseScript);
  127. return (
  128. <Animated.View
  129. style={[
  130. styles.container,
  131. {
  132. opacity: enableAnimation ? this.opacityAnimatedValue : 1,
  133. width,
  134. height: height + heightOffset
  135. },
  136. style
  137. ]}
  138. >
  139. <WebView
  140. renderError={renderError}
  141. mediaPlaybackRequiresUserAction={mediaPlaybackRequiresUserAction}
  142. bounces={bounces}
  143. decelerationRate={decelerationRate}
  144. allowsInlineMediaPlayback={allowsInlineMediaPlayback}
  145. dataDetectorTypes={dataDetectorTypes}
  146. originWhitelist={originWhitelist || ['*']}
  147. ref={this.webView}
  148. onMessage={onMessage}
  149. onError={onError}
  150. onLoad={onLoad}
  151. onLoadStart={onLoadStart}
  152. onLoadEnd={onLoadEnd}
  153. onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
  154. style={styles.webView}
  155. scrollEnabled={!!scrollEnabled}
  156. injectedJavaScript={script}
  157. source={source}
  158. onNavigationStateChange={this.handleNavigationStateChange}
  159. />
  160. </Animated.View>
  161. );
  162. }
  163. }
  164. const styles = StyleSheet.create({
  165. container: {
  166. backgroundColor: 'transparent'
  167. },
  168. webView: {
  169. backgroundColor: 'transparent',
  170. flex: 1
  171. }
  172. });
  173. // add viewport setting to meta for WKWebView
  174. const commonScript = `
  175. var meta = document.createElement('meta');
  176. meta.setAttribute('name', 'viewport');
  177. meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);
  178. updateSize();
  179. window.addEventListener('load', updateSize);
  180. window.addEventListener('resize', updateSize);
  181. `;
  182. function getBaseScript(style) {
  183. return `
  184. ;
  185. ${getCurrentSize}
  186. (function () {
  187. if (!document.getElementById("rnahw-wrapper")) {
  188. var height = 0;
  189. var width = ${getWidth(style)};
  190. var wrapper = document.createElement('div');
  191. wrapper.id = 'rnahw-wrapper';
  192. while (document.body.firstChild instanceof Node) {
  193. wrapper.appendChild(document.body.firstChild);
  194. }
  195. document.body.appendChild(wrapper);
  196. function updateSize() {
  197. if (document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  198. var size = getSize(wrapper);
  199. height = size.height;
  200. width = size.width;
  201. document.title = height.toString() + ',' + width.toString();
  202. }
  203. }
  204. ${commonScript}
  205. ${domMutationObserveScript}
  206. }
  207. } ());
  208. `;
  209. }
  210. function getIframeBaseScript(style) {
  211. return `
  212. ;
  213. ${getCurrentSize}
  214. (function () {
  215. var height = 0;
  216. var width = ${getWidth(style)};
  217. function updateSize() {
  218. if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
  219. var size = getSize(document.body.firstChild);
  220. height = size.height;
  221. width = size.width;
  222. document.title = height.toString() + ',' + width.toString();
  223. }
  224. }
  225. ${commonScript}
  226. ${domMutationObserveScript}
  227. } ());
  228. `;
  229. }