Setting iframe height to scrollHeight in ReactJS
Asked Answered
M

7

14
  • The typical solution to the problem doesn't work in in React due to its dynamically generated component structure and event model, as opposed to traditional static HTML:

script:

<script>
  function resizeIframe(obj) {
    obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
  }
</script>

html:

<iframe src="..." frameborder="0" scrolling="no" onload="resizeIframe(this)" />
  • There is a npm package react-iframe, but it looks unfinished (accepts only props url, width, height):

    https://www.npmjs.com/package/react-iframe

  • The likely part of the solution is to listen to the load event of the iframe, but in a way that is compatible with React.

Is there a way in React to set the height of an iframe to the height of its scrollable contents?

my code:

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import Iframe from 'react-iframe'

export default class FullheightIframe extends Component {

    componentDidMount() {
        console.log("IFRAME DID MOUNT");
    }

    renderReactFrame() {
        return (
            <Iframe url="http://www.example.com" width="100%" height="100%" onLoad={()=>{console.log("IFRAME ON LOAD")}}></Iframe>
        );
    }

    renderHTMLFrame() {
        return (
            <iframe 
                onLoad={(loadEvent)=>{
                    // NOT WORKING var frameBody = ReactDOM.findDOMNode(this).contentDocument.body; // contentDocument undefined
                    // NOT WORKING obj.nativeEvent.contentWindow.document.body.scrollHeight // contentWindow undefined
                }} 
                ref="iframe" 
                src="http://www.example.com" 
                width="100%" 
                height="100%" 
                scrolling="no" 
                frameBorder="0"
            />
        );
    }

    render() {
        return (
            <div style={{maxWidth:640, width:'100%', height:'100%', overflow:'auto'}}>
                {this.renderHTMLFrame()}
            </div>
        );
    }
}
Millner answered 4/2, 2017 at 16:47 Comment(0)
H
4

What you want to do is in your componentDidMount, run the script to set the height. [If you are loading external content, you might want to add event listener on the IFrame to wait until the external content is loaded.]

   componentDidMount() {
       const obj = ReactDOM.findDOMNode(this);
       obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
    }

There is another, more "reacty" way of doing this - where you would store the height in state.

   componentDidMount() {
       const obj = ReactDOM.findDOMNode(this);
       this.setState({iFrameHeight:  obj.contentWindow.document.body.scrollHeight + 'px'});
    }

and then in your render:

render() {
    return (
        <div style={{maxWidth:640, width:'100%', height:this.state.iFrameHeight, overflow:'auto'}}>
            {this.renderHTMLFrame()}
        </div>
    );
}
Hadley answered 4/2, 2017 at 18:44 Comment(3)
This solution should work, but it doesn't (not finding obj): Uncaught TypeError: Cannot read property 'document' of undefinedMillner
Update: This was fixed by using the iframe as root element in the component's return method. Now the problem is that height is being set to 8px. Probably because the iframe is yet not being fully loaded.Millner
Cannot read property 'document' of undefined - that means contentWindow is not found - that means iFrame is not loaded. That means you need to add event listener to iFrame - iframe.addEventListener('load', function() { ...Hadley
M
12

Here is the answer, but first two important things.

  • Iframe has to be the root component in the render() method
  • The height has to be captured from the onLoad event (once the iframe if fully loaded)

Here is the full code:

import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'

export default class FullheightIframe extends Component {

    constructor() {
        super();
        this.state = {
            iFrameHeight: '0px'
        }
    }

    render() {
        return (
            <iframe 
                style={{maxWidth:640, width:'100%', height:this.state.iFrameHeight, overflow:'visible'}}
                onLoad={() => {
                    const obj = ReactDOM.findDOMNode(this);
                    this.setState({
                        "iFrameHeight":  obj.contentWindow.document.body.scrollHeight + 'px'
                    });
                }} 
                ref="iframe" 
                src="http://www.example.com" 
                width="100%" 
                height={this.state.iFrameHeight} 
                scrolling="no" 
                frameBorder="0"
            />
        );
    }
}
Millner answered 4/2, 2017 at 20:51 Comment(4)
This doesnt work in cross origin. Anyone know how to do it in cross origin?Competence
@Competence most iframe related resizing tools aren't going to work cross-origin. for cross-origin, the third party page has to have code added to it that authorizes and handles communication to the parent page.Blatt
@Competence Take a look at npmjs.com/package/iframe-resizer-react for cross origin iframe resizingVanish
@DavidBradshaw Thank you!Mitchel
B
8

A couple of things to note here:

  • You can use refs to get a reference to the iframe instead of having to search for it
  • Use the onLoad() handler from the iframe to ensure that the content has loaded before you try to resize it - if you try to use React's lifecycle methods like componentDidMount() you run the risk of the content not being present yet.
  • You will likely also want a resize handler to ensure the iframe gets resized as needed - just be sure to clean it up when the component unmounts.
  • You have to be careful of how different browsers report the height. Go for the largest you can find.
  • You may have issues if the iframe content is in a different domain than your code. There are solutions out there such as react-iframe-resizer-super that try to solve this problem in a cross-domain compatible way.

class WrappedFrame extends React.Component {
  state = { contentHeight: 100 };

  handleResize = () => {
    const { body, documentElement } = this.container.contentWindow.document;
    const contentHeight = Math.max(
      body.clientHeight,
      body.offsetHeight,
      body.scrollHeight,
      documentElement.clientHeight,
      documentElement.offsetHeight,
      documentElement.scrollHeight
    );
    if (contentHeight !== this.state.contentHeight) this.setState({ contentHeight });
  };
  
  onLoad = () => {
    this.container.contentWindow.addEventListener('resize', this.handleResize);
    this.handleResize();
  }
  
  componentWillUnmount() {
    this.container.contentWindow.removeEventListener('resize', this.handleResize);
  }
  
  render() {
    const { contentHeight } = this.state;
    return (
      <iframe
        frameBorder="0"
        onLoad={this.onLoad}
        ref={(container) => { this.container = container; }}
        scrolling="no"
        src="your.source"
        style={{ width: '100%', height: `${contentHeight}px` }}
        title="Some Content"
      />
    );
  }
}

In this example we're storing the determined content height in the component's state and using that state to set the height of the rendered iframe. Also, by putting the onLoad() handler definition in the component, you save a tiny bit of performance in render() by not creating a new handler function on every re-render.

Brubeck answered 13/6, 2018 at 18:6 Comment(1)
This worked well for me. Notice missing comma in handleResize function in the contentHeight definition. Can't edit myself — edits has to be 6 characters.Tyree
H
4

What you want to do is in your componentDidMount, run the script to set the height. [If you are loading external content, you might want to add event listener on the IFrame to wait until the external content is loaded.]

   componentDidMount() {
       const obj = ReactDOM.findDOMNode(this);
       obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
    }

There is another, more "reacty" way of doing this - where you would store the height in state.

   componentDidMount() {
       const obj = ReactDOM.findDOMNode(this);
       this.setState({iFrameHeight:  obj.contentWindow.document.body.scrollHeight + 'px'});
    }

and then in your render:

render() {
    return (
        <div style={{maxWidth:640, width:'100%', height:this.state.iFrameHeight, overflow:'auto'}}>
            {this.renderHTMLFrame()}
        </div>
    );
}
Hadley answered 4/2, 2017 at 18:44 Comment(3)
This solution should work, but it doesn't (not finding obj): Uncaught TypeError: Cannot read property 'document' of undefinedMillner
Update: This was fixed by using the iframe as root element in the component's return method. Now the problem is that height is being set to 8px. Probably because the iframe is yet not being fully loaded.Millner
Cannot read property 'document' of undefined - that means contentWindow is not found - that means iFrame is not loaded. That means you need to add event listener to iFrame - iframe.addEventListener('load', function() { ...Hadley
V
2

This npm package will do what you what, it offers a range of different ways to calculate the height of the content in the iframe

https://www.npmjs.com/package/iframe-resizer-react

With this use case it can be configured as follows

<IframeResizer
    heightCalculationMethod="bodyScroll"
    src="http://anotherdomain.com/iframe.html"
/>
Vanish answered 9/11, 2019 at 10:22 Comment(2)
Except IframeResizer "The page in the iframe then needs (iframeResizer.contentWindow.min.js) from iframe-resizer." so that's no good if its not your page loading in the Iframe!Pepperandsalt
@Pepperandsalt either you have some control over the iFrame, or you probably shouldn't be trying to interfere with it. The script in the iframe is designed to be a good guest on someone else's site, if you need to ask a third party to host it for you.Vanish
E
1

None of the answers proposed so far worked for me. The hackish approach of doing a short setTimeout from within onLoad kind-of seems to do the job, at least in my case.

class SmartIFrame extends React.Component {
    render() {
        return <iframe srcDoc={this.props.srcDoc}
                       scrolling="no"
                       frameBorder={0}
                       width="100%"
                       onLoad = {e => setTimeout(() => {
                           const obj = ReactDOM.findDOMNode(this);
                           obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
                       }, 50)}/>
    }
}
Exhibitionist answered 21/5, 2018 at 21:51 Comment(0)
M
1

Just use useState and useEffect with setTimeout with 100miliseconds and you are done

 const [frameHeight , setFrameHeight] = useState()

useEffect(() => {

 const frame = document.getElementById('myFrame');
 console.log("height" , frame.contentWindow.document.body.scrollHeight + "px")
        
 setTimeout(() => {
   setFrameHeight(frame.contentWindow.document.body.scrollHeight + "px")
  },100)


 },[])
       return (
            <iframe srcdoc={content}
            id="myFrame"
            width="100%" 
            height={frameHeight}
            frameBorder="0"
            scrolling="no"
            ></iframe>
     )

You'r Welcome !

Marchpane answered 18/10, 2021 at 14:7 Comment(0)
I
0

The best and reliable way to fit iframe is to use

iframe-resizer package.

https://www.npmjs.com/package/iframe-resizer
Inflation answered 31/8, 2021 at 5:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.