123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- 'use strict'
-
- import React, { PureComponent } from 'react';
-
- import {
- Animated,
- Dimensions,
- StyleSheet,
- View,
- ViewPropTypes,
- WebView
- } from 'react-native';
-
- import PropTypes from 'prop-types';
-
- export default class AutoHeightWebView extends PureComponent {
- static propTypes = {
- hasIframe: PropTypes.bool,
- source: WebView.propTypes.source,
- onHeightUpdated: PropTypes.func,
- customScript: PropTypes.string,
- customStyle: PropTypes.string,
- enableAnimation: PropTypes.bool,
- // 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,
- style: ViewPropTypes.style,
- // rn WebView callback
- onError: PropTypes.func,
- onLoad: PropTypes.func,
- onLoadStart: PropTypes.func,
- onLoadEnd: PropTypes.func,
- onShouldStartLoadWithRequest: PropTypes.func,
- // add web/files... to project root
- files: PropTypes.arrayOf(PropTypes.shape({
- href: PropTypes.string,
- type: PropTypes.string,
- rel: PropTypes.string
- }))
- }
-
- static defaultProps = {
- scalesPageToFit: false,
- enableAnimation: true,
- animationDuration: 555,
- heightOffset: 12
- }
-
- constructor(props) {
- super(props);
- this.handleNavigationStateChange = this.handleNavigationStateChange.bind(this);
- if (this.props.enableAnimation) {
- this.opacityAnimatedValue = new Animated.Value(0);
- }
- let initialScript = props.hasIframe ? IframeBaseScript : BaseScript;
- initialScript = props.files
- ? this.appendFilesToHead(props.files, BaseScript)
- : BaseScript;
- initialScript = props.customStyle
- ? this.appendStylesToHead(props.customStyle, initialScript)
- : initialScript;
- this.state = {
- height: 0,
- script: initialScript
- };
- }
-
- componentWillReceiveProps(nextProps) {
- let currentScript = nextProps.hasIframe ? IframeBaseScript : BaseScript;
- if (nextProps.files) {
- currentScript = this.appendFilesToHead(nextProps.files, currentScript);
- }
- currentScript = nextProps.customStyle
- ? this.appendStylesToHead(nextProps.customStyle, currentScript)
- : currentScript;
- this.setState({ script: currentScript });
- }
-
- appendFilesToHead(files, script) {
- if (!files) {
- return script;
- }
- return files.reduceRight((file, combinedScript) => `
- var link = document.createElement('link');
- link.rel = '${file.rel}';
- link.type = '${file.type}';
- link.href = '${file.href}';
- document.head.appendChild(link);
- ${combinedScript}
- `, script);
- }
-
- appendStylesToHead(styles, script) {
- if (!styles) {
- return script;
- }
- // Escape any single quotes or newlines in the CSS with .replace()
- const escaped = styles.replace(/\'/g, "\\'").replace(/\n/g, '\\n')
- return `
- var styleElement = document.createElement('style');
- var styleText = document.createTextNode('${escaped}');
- styleElement.appendChild(styleText);
- document.head.appendChild(styleElement);
- ${script}
- `;
- }
-
- onHeightUpdated(height) {
- if (this.props.onHeightUpdated) {
- this.props.onHeightUpdated(height);
- }
- }
-
- handleNavigationStateChange(navState) {
- const height = Number(navState.title);
- if (height && height !== this.state.height) {
- if (this.props.enableAnimation) {
- this.opacityAnimatedValue.setValue(0);
- }
- this.setState({ height }, () => {
- if (this.props.enableAnimation) {
- Animated.timing(this.opacityAnimatedValue, {
- toValue: 1,
- duration: this.props.animationDuration
- }).start(() => this.onHeightUpdated(height));
- }
- else {
- this.onHeightUpdated(height);
- }
- });
- }
- }
-
- render() {
- const { height, script } = this.state;
- const { onError, onLoad, onLoadStart, onLoadEnd, onShouldStartLoadWithRequest, scalesPageToFit, enableAnimation, source, heightOffset, customScript, style } = this.props;
- const webViewSource = Object.assign({}, source, { baseUrl: 'web/' });
- return (
- <Animated.View style={[Styles.container, {
- opacity: enableAnimation ? this.opacityAnimatedValue : 1,
- height: height + heightOffset,
- }, style]}>
- <WebView
- onError={onError}
- onLoad={onLoad}
- onLoadStart={onLoadStart}
- onLoadEnd={onLoadEnd}
- onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
- style={Styles.webView}
- injectedJavaScript={script + customScript}
- scrollEnabled={false}
- scalesPageToFit={scalesPageToFit}
- source={webViewSource}
- onNavigationStateChange={this.handleNavigationStateChange} />
- </Animated.View>
- );
- }
- }
-
- const ScreenWidth = Dimensions.get('window').width;
-
- const Styles = StyleSheet.create({
- container: {
- width: ScreenWidth,
- backgroundColor: 'transparent'
- },
- webView: {
- flex: 1,
- backgroundColor: 'transparent'
- }
- });
-
- const BaseScript =
- `
- ;
- (function () {
- var i = 0;
- var height = 0;
- var wrapper = document.createElement('div');
- wrapper.id = 'height-wrapper';
- while (document.body.firstChild) {
- wrapper.appendChild(document.body.firstChild);
- }
- document.body.appendChild(wrapper);
- function updateHeight() {
- if(document.body.offsetHeight !== height) {
- height = wrapper.clientHeight;
- document.title = wrapper.clientHeight;
- window.location.hash = ++i;
- }
- }
- updateHeight();
- window.addEventListener('load', updateHeight);
- window.addEventListener('resize', updateHeight);
- } ());
- `;
-
- const IframeBaseScript =
- `
- ;
- (function () {
- var i = 0;
- var height = 0;
- function updateHeight() {
- if(document.body.offsetHeight !== height) {
- height = document.body.firstChild.clientHeight;
- document.title = document.body.firstChild.clientHeight;
- window.location.hash = ++i;
- }
- }
- updateHeight();
- window.addEventListener('load', updateHeight);
- window.addEventListener('resize', updateHeight);
- } ());
- `;
|