Skip to content

Instantly share code, notes, and snippets.

@esamattis
Last active February 11, 2022 16:01
Show Gist options
  • Save esamattis/10c77c1710dd137a1335 to your computer and use it in GitHub Desktop.
Save esamattis/10c77c1710dd137a1335 to your computer and use it in GitHub Desktop.
React native: Is it possible to have the height of a html content in a webview? http://stackoverflow.com/q/32952270
/*
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2016 Esa-Matti Suuronen <esa-matti@suuronen.org>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
*/
import React, {WebView, View, Text} from "react-native";
const BODY_TAG_PATTERN = /\<\/ *body\>/;
// Do not add any comments to this! It will break because all line breaks will removed for
// some weird reason when this script is injected.
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;
@Durkaen
Copy link

Durkaen commented Sep 15, 2019

@Aragorn450 Thank you very much, your comment helped a lot. Based on it I've written a small WebViewAutoHeight-component, that makes use of the postMessage-function:

import React from 'react';
import { WebView } from 'react-native';

export default class WebViewAutoHeight extends React.Component {
  state = {
    contentHeight: 1,   //by some reason, this may not be 0 at the beginning
  }

  render() {
    const { source } = this.props;

    return (
      <WebView
        source={source}
        scrollEnabled={false}

        injectedJavaScript={"setTimeout(function() { window.postMessage(document.body.scrollHeight, '*'); }, 1000);"}
        onMessage={(event) => {
          this.setState({ contentHeight: parseInt(event.nativeEvent.data) });
        }}
        style={{height: this.state.contentHeight}}
      />
    );
  }
}

I hope it helps!

Many thanks, works perfectly!

@collinxz-coder
Copy link

import React, { PureComponent } from 'react';
import { WebView } from 'react-native-webview';
import { View, Text, ScrollView } from 'react-native';

const html = `
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        img {
            display: block;
            max-width: 100% !important;
            height: auto !important;
        }
    </style>
</head>

<body>
    <table>
        <tbody>
            <tr class="firstRow">
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
            </tr>
            <tr>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
            </tr>
            <tr>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
                <td width="260" valign="top" style="word-break: break-all;">
                    test
                </td>
            </tr>
            <tr>
                <td valign="top" colspan="1" rowspan="1" style="word-break: break-all;">
                    test
                </td>
                <td valign="top" colspan="1" rowspan="1" style="word-break: break-all;">
                    test
                </td>
                <td valign="top" colspan="1" rowspan="1" style="word-break: break-all;">
                    test
                </td>
            </tr>
        </tbody>
    </table>
    <p>
        <img src="http://p6.qhimg.com/bdr/__85/t018936bde4a8278077.jpg" />
    </p>
    <p>
        <br />
    </p>
    <ul style="margin-top: 1.5em; margin-bottom: 1.5em; padding: 0px 0px 0px 2em; list-style-position: outside; list-style-image: initial; color: rgb(51, 51, 51); font-family: -apple-system, system-ui, &quot;San Francisco&quot;, &quot;Segoe UI&quot;, Roboto, Ubuntu, &quot;Helvetica Neue&quot;, Arial, sans-serif; font-size: 14px; white-space: normal; background-color: rgb(255, 255, 255);"
        class=" list-paddingleft-2">
        <li>
            <p style="margin-top: 0px; margin-bottom: 0px;">
                <code
                    style="font-family: var(--monoFont); font-size: 0.9em; color: var(--textColor); white-space: pre-wrap; direction: ltr; tab-size: 2; margin: 0px 1px; padding: 1px 4px 2px; background: var(--labelBackground); border-radius: 3px;">cover</code>:
                Scale the image uniformly (maintain the image&#39;s aspect ratio) so that both dimensions (width and
                height) of the image will be equal to or larger than the corresponding dimension of the view (minus
                padding).
            </p>
        </li>
        <li>
            <p style="margin-top: 0px; margin-bottom: 0px;">
                <code
                    style="font-family: var(--monoFont); font-size: 0.9em; color: var(--textColor); white-space: pre-wrap; direction: ltr; tab-size: 2; margin: 0px 1px; padding: 1px 4px 2px; background: var(--labelBackground); border-radius: 3px;">contain</code>:
                Scale the image uniformly (maintain the image&#39;s aspect ratio) so that both dimensions (width and
                height) of the image will be equal to or less than the corresponding dimension of the view (minus
                padding).
            </p>
        </li>
        <li>
            <p style="margin-top: 0px; margin-bottom: 0px;">
                <code
                    style="font-family: var(--monoFont); font-size: 0.9em; color: var(--textColor); white-space: pre-wrap; direction: ltr; tab-size: 2; margin: 0px 1px; padding: 1px 4px 2px; background: var(--labelBackground); border-radius: 3px;">stretch</code>:
                Scale width and height independently, This may change the aspect ratio of the src.
            </p>
        </li>
        <li>
            <p style="margin-top: 0px; margin-bottom: 0px;">
                <code
                    style="font-family: var(--monoFont); font-size: 0.9em; color: var(--textColor); white-space: pre-wrap; direction: ltr; tab-size: 2; margin: 0px 1px; padding: 1px 4px 2px; background: var(--labelBackground); border-radius: 3px;">repeat</code>:
                Repeat the image to cover the frame of the view. The image will keep its size and aspect ratio, unless
                it is larger than the view, in which case it will be scaled down uniformly so that it is contained in
                the view.
            </p>
        </li>
        <li>
            <p style="margin-top: 0px; margin-bottom: 0px;">
                <code
                    style="font-family: var(--monoFont); font-size: 0.9em; color: var(--textColor); white-space: pre-wrap; direction: ltr; tab-size: 2; margin: 0px 1px; padding: 1px 4px 2px; background: var(--labelBackground); border-radius: 3px;">center</code>:
                Center the image in the view along both dimensions. If the image is larger than the view, scale it down
                uniformly so that it is contained in the view.
            </p>
        </li>
    </ul>
    <p>
        <br />
    </p>
</body>

</html>
`;

export default class TestPage extends PureComponent {
    constructor(props) {
        super(props);

        this.state = { height: 0 };
    }

    _onWebViewMessage = (event) => {
        console.log(event);
        this.setState({ height: Number(event.nativeEvent.data) })
    }

    onWebViewMessage = (event) => {
        this.setState({ height: Number(event.nativeEvent.data) })
    }

    render() {
        return (
            <ScrollView>
                <View style={{ height: 200, backgroundColor: 'yellow'}}></View>
                <WebView
                    style={{ height: this.state.height }}
                    originWhitelist={['*']}
                    source={{ html: html }}
                    onMessage={this.onWebViewMessage}
                    injectedJavaScript='window.ReactNativeWebView.postMessage(document.documentElement.scrollHeight)'
                />
                <View style={{ height: 200, backgroundColor: 'green'}}></View>
            </ScrollView>
        )
    }
}

@sebastianor
Copy link

I have used @cinder92 method and i have extra space below, my html code. Anyone had similar problem ?

<WebViewAutoHeight 
useWebKit={true} 
 javaScriptEnabled={true} 
domStorageEnabled={true} 
startInLoadingState={true} 
originWhitelist={['*']} 
style={styles.contentStyles}  
source={{html: this.props.InformationContent}}>
</WebViewAutoHeight>

  contentStyles:{
      paddingBottom: 20,
      width: "100%",
  }

@Rendfold
Copy link

Rendfold commented Nov 12, 2019

I can confirm whitespaces. Still haven't found solution. Any idea?
@sebastianor did you solve it?

@sebastianor
Copy link

I found something. Try to add <meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'> it works for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment