123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- 'use strict';
-
- import React, { PureComponent } from 'react';
-
- import { Animated, StyleSheet, ViewPropTypes, WebView } from 'react-native';
-
- import PropTypes from 'prop-types';
-
- import {
- isEqual,
- setState,
- getWidth,
- isSizeChanged,
- handleSizeUpdated,
- domMutationObserveScript,
- getCurrentSize
- } from './common.js';
-
- import momoize from './momoize';
-
- export default class AutoHeightWebView extends PureComponent {
- static propTypes = {
- hasIframe: PropTypes.bool,
- onNavigationStateChange: PropTypes.func,
- onMessage: PropTypes.func,
- source: WebView.propTypes.source,
- customScript: PropTypes.string,
- customStyle: PropTypes.string,
- enableAnimation: PropTypes.bool,
- style: ViewPropTypes.style,
- scrollEnabled: PropTypes.bool,
- // either height or width updated will trigger this
- onSizeUpdated: PropTypes.func,
- // if set to true may cause some layout issues (smaller font size)
- scalesPageToFit: PropTypes.bool,
- // only works on enable animation
- animationDuration: PropTypes.number,
- // offset of rn webview margin
- heightOffset: PropTypes.number,
- // rn WebView callback
- onError: PropTypes.func,
- onLoad: PropTypes.func,
- onLoadStart: PropTypes.func,
- onLoadEnd: PropTypes.func,
- onShouldStartLoadWithRequest: PropTypes.func,
- // 'web/' by default
- baseUrl: PropTypes.string,
- // add baseUrl/files... to project root
- files: PropTypes.arrayOf(
- PropTypes.shape({
- href: PropTypes.string,
- type: PropTypes.string,
- rel: PropTypes.string
- })
- )
- };
-
- static defaultProps = {
- baseUrl: 'web/',
- scalesPageToFit: false,
- enableAnimation: true,
- animationDuration: 255,
- heightOffset: 12
- };
-
- constructor(props) {
- super(props);
- const { enableAnimation, style } = props;
- enableAnimation && (this.opacityAnimatedValue = new Animated.Value(0));
- this.webView = React.createRef();
- this.state = {
- isSizeChanged: false,
- width: getWidth(style),
- height: style && style.height ? style.height : 0
- };
- }
-
- getUpdatedState = momoize(setState, isEqual);
-
- static getDerivedStateFromProps(props, state) {
- const { height: oldHeight, width: oldWidth } = state;
- const height = props.style ? props.style.height : null;
- const width = props.style ? props.style.width : null;
- if (isSizeChanged(height, oldHeight, width, oldWidth)) {
- return {
- height,
- width,
- isSizeChanged: true
- };
- }
- return null;
- }
-
- componentDidUpdate() {
- const { height, width, isSizeChanged } = this.state;
- if (isSizeChanged) {
- const { enableAnimation, animationDuration, onSizeUpdated } = this.props;
- if (enableAnimation) {
- Animated.timing(this.opacityAnimatedValue, {
- toValue: 1,
- duration: animationDuration
- }).start(() => {
- handleSizeUpdated(height, width, onSizeUpdated);
- });
- } else {
- handleSizeUpdated(height, width, onSizeUpdated);
- }
- this.setState({ isSizeChanged: false });
- }
- }
-
- handleNavigationStateChange = navState => {
- const { title } = navState;
- const { onNavigationStateChange } = this.props;
- if (!title) {
- onNavigationStateChange && onNavigationStateChange(navState);
- return;
- }
- const [heightValue, widthValue] = title.split(',');
- const width = Number(widthValue);
- const height = Number(heightValue);
- const { height: oldHeight, width: oldWidth } = this.state;
- if (isSizeChanged(height, oldHeight, width, oldWidth)) {
- this.props.enableAnimation && this.opacityAnimatedValue.setValue(0);
- this.setState({
- isSizeChanged: true,
- height,
- width
- });
- }
- onNavigationStateChange && onNavigationStateChange(navState);
- };
-
- stopLoading() {
- this.webView.current.stopLoading();
- }
-
- render() {
- const { height, width } = this.state;
- const {
- onMessage,
- onError,
- onLoad,
- onLoadStart,
- onLoadEnd,
- onShouldStartLoadWithRequest,
- scalesPageToFit,
- enableAnimation,
- heightOffset,
- style,
- scrollEnabled
- } = this.props;
- const { source, script } = this.getUpdatedState(this.props, getBaseScript, getIframeBaseScript);
- return (
- <Animated.View
- style={[
- styles.container,
- {
- opacity: enableAnimation ? this.opacityAnimatedValue : 1,
- width,
- height: height + heightOffset
- },
- style
- ]}
- >
- <WebView
- originWhitelist={['*']}
- ref={this.webView}
- onMessage={onMessage}
- onError={onError}
- onLoad={onLoad}
- onLoadStart={onLoadStart}
- onLoadEnd={onLoadEnd}
- onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
- style={styles.webView}
- scrollEnabled={!!scrollEnabled}
- scalesPageToFit={scalesPageToFit}
- injectedJavaScript={script}
- source={source}
- onNavigationStateChange={this.handleNavigationStateChange}
- />
- </Animated.View>
- );
- }
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'transparent'
- },
- webView: {
- flex: 1,
- backgroundColor: 'transparent'
- }
- });
-
- const commonScript = `
- updateSize();
- window.addEventListener('load', updateSize);
- window.addEventListener('resize', updateSize);
- `;
-
- function getBaseScript(style) {
- return `
- ;
- ${getCurrentSize}
- (function () {
- if (!document.getElementById("rnahw-wrapper")) {
- var height = 0;
- var width = ${getWidth(style)};
- var wrapper = document.createElement('div');
- wrapper.id = 'rnahw-wrapper';
- while (document.body.firstChild instanceof Node) {
- wrapper.appendChild(document.body.firstChild);
- }
- document.body.appendChild(wrapper);
- function updateSize() {
- if (document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
- var size = getSize(wrapper);
- height = size.height;
- width = size.width;
- document.title = height.toString() + ',' + width.toString();
- }
- }
- ${commonScript}
- ${domMutationObserveScript}
- }
- } ());
- `;
- }
-
- function getIframeBaseScript(style) {
- return `
- ;
- ${getCurrentSize}
- (function () {
- var height = 0;
- var width = ${getWidth(style)};
- function updateSize() {
- if(document.body.offsetHeight !== height || document.body.offsetWidth !== width) {
- var size = getSize(document.body.firstChild);
- height = size.height;
- width = size.width;
- document.title = height.toString() + ',' + width.toString();
- }
- }
- ${commonScript}
- ${domMutationObserveScript}
- } ());
- `;
- }
|