React Native自动高度WebView在android上不起作用

时间:2016-03-17 07:21:55

标签: webview android-webview react-native

我正在实现具有动态高度的WebView。我发现这个解决方案就像iOS上的魅力一样,并且不适用于android。该解决方案使用WV中的JS将标题设置为内容高度的值。这是代码:

...
this.state = {webViewHeight: 0};
...
<WebView
    source={{html: this.wrapWevViewHtml(this.state.content)}}
    style={{width: Dimensions.get('window').width - 20, height: this.state.webViewHeight}}
    scrollEnabled={false}
    javaScriptEnabled={true}
    injectedJavaScript="window.location.hash = 1;document.title = document.height;"
    onNavigationStateChange={this.onWebViewNavigationStateChange.bind(this)}
/>
...
onWebViewNavigationStateChange(navState) {
    // navState.title == height on iOS and html content on android
    if (navState.title) {
        this.setState({
            webViewHeight: Number(navState.title)
        });
    }
}
...

但在Android上,onWebViewNavigationStateChange内的标题值等于页面内容。

我做错了什么?

2 个答案:

答案 0 :(得分:5)

我也对此感到困惑。它确实有效,但很难调试它为什么不起作用,因为Android上的React Native WebViews没有启用Chrome远程调试。

我有两个问题:

  1. 我注入Webview的脚本包含一些单行注释,在Android上删除了所有换行符(另一个错误?)。它导致WebView中出现语法错误。

  2. 在第一次调用时,标题内容确实是Webview的完整内容。不知道为什么,但后来称它为高度。所以只需处理这种情况。

  3. 以下是我现在使用的代码,它适用于Android和iOS上的React Native 0.22

    import React, {WebView, View, Text} from "react-native";
    
    
    const BODY_TAG_PATTERN = /\<\/ *body\>/;
    
    // Do not add any comments to this! It will break line breaks will removed for
    // some weird reason.
    var script = `
    ;(function() {
    var wrapper = document.createElement("div");
    wrapper.id = "height-wrapper";
    while (document.body.firstChild) {
        wrapper.appendChild(document.body.firstChild);
    }
    
    document.body.appendChild(wrapper);
    
    var i = 0;
    function updateHeight() {
        document.title = wrapper.clientHeight;
        window.location.hash = ++i;
    }
    updateHeight();
    
    window.addEventListener("load", function() {
        updateHeight();
        setTimeout(updateHeight, 1000);
    });
    
    window.addEventListener("resize", updateHeight);
    }());
    `;
    
    
    const style = `
    <style>
    body, html, #height-wrapper {
        margin: 0;
        padding: 0;
    }
    #height-wrapper {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
    }
    </style>
    <script>
    ${script}
    </script>
    `;
    
    const codeInject = (html) => html.replace(BODY_TAG_PATTERN, style + "</body>");
    
    
    /**
     * Wrapped Webview which automatically sets the height according to the
     * content. Scrolling is always disabled. Required when the Webview is embedded
     * into a ScrollView with other components.
     *
     * Inspired by this SO answer http://stackoverflow.com/a/33012545
     * */
    var WebViewAutoHeight = React.createClass({
    
        propTypes: {
            source: React.PropTypes.object.isRequired,
            injectedJavaScript: React.PropTypes.string,
            minHeight: React.PropTypes.number,
            onNavigationStateChange: React.PropTypes.func,
            style: WebView.propTypes.style,
        },
    
        getDefaultProps() {
            return {minHeight: 100};
        },
    
        getInitialState() {
            return {
                realContentHeight: this.props.minHeight,
            };
        },
    
        handleNavigationChange(navState) {
            if (navState.title) {
                const realContentHeight = parseInt(navState.title, 10) || 0; // turn NaN to 0
                this.setState({realContentHeight});
            }
            if (typeof this.props.onNavigationStateChange === "function") {
                this.props.onNavigationStateChange(navState);
            }
        },
    
        render() {
            const {source, style, minHeight, ...otherProps} = this.props;
            const html = source.html;
    
            if (!html) {
                throw new Error("WebViewAutoHeight supports only source.html");
            }
    
            if (!BODY_TAG_PATTERN.test(html)) {
                throw new Error("Cannot find </body> from: " + html);
            }
    
            return (
                <View>
                    <WebView
                        {...otherProps}
                        source={{html: codeInject(html)}}
                        scrollEnabled={false}
                        style={[style, {height: Math.max(this.state.realContentHeight, minHeight)}]}
                        javaScriptEnabled
                        onNavigationStateChange={this.handleNavigationChange}
                    />
                    {process.env.NODE_ENV !== "production" &&
                    <Text>Web content height: {this.state.realContentHeight}</Text>}
                </View>
            );
        },
    
    });
    
    
    export default WebViewAutoHeight;
    

    作为要点https://gist.github.com/epeli/10c77c1710dd137a1335

答案 1 :(得分:0)

在设备上加载本地HTML文件并注入JS是我在Android中正确设置标题/哈希的唯一方法。

/app/src/main/assets/blank.html

<!doctype html>
<html>
<head>
<title id="title">Go Web!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div id="content"></div>
<script>
var content = document.getElementById('content');

var fireEvent = function(event, data) {
  document.title = data;
  window.location.hash = event;
};

var setContent = function(html) {
  content.innerHTML = html;
};
</script>
</body>
</html>

组件

class ResizingWebView extends Component {
  constructor(props) {
    super(props)

    this.state = {
      height: 0
    }
  }
  onNavigationStateChange(navState) {
    var event = navState.url.split('#')[1]
    var data = navState.title

    console.log(event, data)
    if (event == 'resize') {
      this.setState({ height: data })
    }
  }
  render() {
    var scripts = "setContent('<h1>Yay!</h1>');fireEvent('resize', '300')";
    return (
      <WebView
        source={{ uri: 'file:///android_asset/blank.html' }}
        injectedJavaScript={ scripts }
        scalesPageToFit={ false }
        style={{ height: this.state.height }}
        onNavigationStateChange={ this.onNavigationStateChange.bind(this) }
      />
    )
  }
}