Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting iframe height to scrollHeight in ReactJS

  • 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>
        );
    }
}
like image 394
Peter G. Avatar asked Feb 04 '17 16:02

Peter G.


2 Answers

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"
            />
        );
    }
}
like image 149
Peter G. Avatar answered Sep 20 '22 06:09

Peter G.


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.

like image 40
Matt Avatar answered Sep 21 '22 06:09

Matt