暂无描述

index.ios.js 6.5KB

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