Ingen beskrivning

index.ios.js 5.8KB

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