-
-
Save esamattis/10c77c1710dd137a1335 to your computer and use it in GitHub Desktop.
/* | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
Version 2, December 2004 | |
Copyright (C) 2016 Esa-Matti Suuronen <[email protected]> | |
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; |
Thanks Great Work!!!
Mark
not work in android ……,my script code do not execute in android
facebook/react-native#6393 in my app it work in android
is it working while keyboard is showing in Android? it doesn't work on my device
Works great, but I needed to change
import React, {WebView, View, Text} from "react-native";
to
import React from "react";
import {WebView, View, Text} from "react-native";
to get it to work!
Works for me! Awesome solution!
Perfect, thanks 💯
Thanks, works like a charm..!!
WORK LIKE A CHARMMMMM
Nope it doesn't work 100%. This is not height actually. You are setting height to the key name called "target" which means "_rootNodeID". So this is just an ID not actual height. So you are simply setting height to "navState.target"
I have added the following code inside my script variable
:
for(var anchors = document.getElementsByTagName("a"), i = 0; i <anchors.length ; i++) anchors[i].onclick = function(t) {
if (t.target.getAttribute("href")) {
document.title = t.target.getAttribute("href") + '#' + Math.floor(Math.random() * 100);
window.location.hash = t.target.getAttribute("href") + '#' + Math.floor(Math.random() * 100);
}
};
So that I can open the anchor links inside my content using external browser ( instead of opening them inside WebView component ), but somehow this solution does not work on some Android devices ( like Galaxy S8, Samsung S7 Edge, Samsung S6 ) but it works on all IOS devices as well as many android devices. For some reason onNavigationStateChange
is not triggered in listed devices when window.location.hash
is being changed.
any idea what can be the reason ?
Same bug here, on Simulator works perfectly. On real android device, height is very big, if real WebView content height is 300PX on Simulator, on real device is 3-4000PX.
for those who are using react-native 0.48+
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {WebView, View, Text} from "react-native";
import Dimensions from 'Dimensions';
import {colors, measures} from '../settings';
const deviceWidth = Dimensions.get('window').width;
const BODY_TAG_PATTERN = /\<\/ *body\>/;
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 = `
<script>
${script}
</script>
`;
const codeInject = (html) => html.replace(BODY_TAG_PATTERN, style + "</body>");
class WebViewAutoHeight extends Component{
constructor(props){
super(props);
this.state = {
realContentHeight: this.props.minHeight
}
}
handleNavigationChange(navState) {
//console.log(navState,'here')
if (navState && 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 {realContentHeight} = this.state;
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);
}
//console.log('wh',Math.max(this.state.realContentHeight, minHeight))
return (
<View style={{height: Math.max(realContentHeight, minHeight)}}>
<WebView
{...otherProps}
source={{html: codeInject(html)}}
scrollEnabled={false}
style={[{height: Math.max(realContentHeight, minHeight)},style]}
javaScriptEnabled
onNavigationStateChange={(data) => {
this.handleNavigationChange(data);
}}
/>
</View>
);
}
}
WebViewAutoHeight.propTypes = {
source: PropTypes.object.isRequired,
injectedJavaScript: PropTypes.string,
minHeight: PropTypes.number,
onNavigationStateChange: PropTypes.func,
style: WebView.propTypes.style
};
WebViewAutoHeight.defaultProps = {
minHeight: 100
};
export default WebViewAutoHeight;
best regards!
@cinder92 thanks man! works for me!
Thanks man.Works perfectly.
@cinder92 Thanks, brother!!! you saved my day :)
@cinder92 Thanks. Works perfectly.
Thanks god, Thanks you. You saved me.
With the release of Chrome 72 for Android on January 29, 2019, this solution has broken. In handleNavigationChange
, navState.title
is set to the full html of the page instead of the height number expected.
Switching to using messages like demonstrated here: https://stackoverflow.com/a/52556053/904125 seems to be working however. We are still testing with as many devices as possible to make sure it works in all instances, but so far it is working well.
@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!
here is thetypescripted and hook(ed) version with react-native-webview package (not react-native's internal webview)
import React, { FunctionComponent, useState } from "react";
import { WebView, WebViewProps } from "react-native-webview";
import { WebViewNavigation } from "react-native-webview/lib/WebViewTypes";
interface HtmlRendererProps extends WebViewProps {
html: string;
width: number;
height?: number;
}
const srcpt = ` (function(window) {
'use strict';
var doc = window.document;
var clickHandler = (e) => {
e.preventDefault();
window.ReactNativeWebView.postMessage(e.target.attributes["href"].value);
e.stopPropagation();
}
var i = 0;
function updateHeight(height) {
doc.title = height;
window.location.hash = ++i;
}
setTimeout(() => {
var links = doc.querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', clickHandler, false);
}
var h = doc.querySelector(".html-wrapper").clientHeight;
updateHeight(h);
}, 100);
})(window);
true;`;
const formatHtml = (html: string) => {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="html-wrapper">
${html}
</div>
</body>
</html>
`;
};
const HtmlRenderer: FunctionComponent<HtmlRendererProps> = (props) => {
const [finalHeight, setFinalHeight] = useState<number>(!props.height ? props.html.length / 2 : props.height);
const handleNavigationChange = (navState: WebViewNavigation) => {
if (navState.title) {
const realContentHeight = parseInt(navState.title, 10) || 0; // turn NaN to 0
setFinalHeight(realContentHeight);
}
};
if (typeof props.onNavigationStateChange === "function") {
const usersHandler = props.onNavigationStateChange;
props.onNavigationStateChange = (navState: WebViewNavigation) => {
handleNavigationChange(navState);
usersHandler(navState);
};
}
if (props.html) {
if (props.html.indexOf("<body>") < 1) {
props.html = formatHtml(props.html);
}
props.source = { html: props.html };
}
props.style = [props.style, {
width: props.width,
height: finalHeight,
}];
return (
<WebView {...props} />
);
};
export default HtmlRenderer;
https://gist.github.com/orhanveli/78a162e3a3d795eaf9d6778f97befcad#file-webviewautoheight-tsx
@paul019 unfortunately, your solution doesn't work for me on ios
UPDATE: I found what was wrong with my code. I use "react-native-webview", therefore, I need to use "window.ReactNativeWebView.postMessage" instead.
@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!
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, "San Francisco", "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", 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'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'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>
)
}
}
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%",
}
I can confirm whitespaces. Still haven't found solution. Any idea?
@sebastianor did you solve it?
I found something. Try to add <meta name='viewport' content='initial-scale=1.0, maximum-scale=1.0'>
it works for me
Doesn't seem to work with the latest iOS 10 though