如何设置反应组件的iframe内容

时间:2016-01-12 12:06:11

标签: javascript iframe reactjs xmlhttprequest

我正在尝试在React组件中设置iframe的内容,但我无法做到。我有一个组件,其中包含一个函数,当iframe完成加载时必须调用该函数。在该函数中,我正在设置内容,但似乎根本没有调用onload函数。我在chrome浏览器中测试它。我正在尝试以下方法:

var MyIframe = React.createClass({
    componentDidMount : function(){
        var iframe = this.refs.iframe.getDOMNode();
        if(iframe.attachEvent){
            iframe.attacheEvent("onload", this.props.onLoad);
        }else{
            iframe.onload = this.props.onLoad;
        }
    },
    render: function(){
        return <iframe ref="iframe" {...this.props}/>;
    }
});

var Display = React.createClass({
    getInitialState : function(){
        return {
            oasData : ""
        };
    },
    iframeOnLoad : function(){
        var iframe = this.refs.bannerIframe;
        iframe.contentDocument.open();
        iframe.contentDocument.write(['<head></head><style>body {margin: 0; overflow: hidden;display:inline-block;} html{ margin: 0 auto; text-align: center;} body > a > img {max-width: 100%; height: inherit;}', extraCss, '</style></head><body>', this.state.oasData.Ad[0].Text, '</body>'].join(''));
        iframe.contentDocument.close();
    },
    setOasData : function(data){
        this.setState({
            oasData : JSON.parse(data)
        });
    },
    componentDidMount : function(){
        var url = "getJsonDataUrl";

        var xhttp = new XMLHttpRequest();
        var changeOasDataFunction = this.setOasData;
        xhttp.onreadystatechange = function () {
            if (xhttp.readyState == 4 && xhttp.status == 200) {
                changeOasDataFunction(xhttp.responseText);
            }
        };
        xhttp.open("GET", url, true);
        xhttp.send();
    },
    render : function(){
        return (
            <MyIframe refs="bannerIframe" onLoad={this.iframeOnLoad} />
        );
    }
});

module.exports = Display;

我做错了什么?

5 个答案:

答案 0 :(得分:27)

修改10-25-2018

随着Portals in React 16的引入,整个框架的内容变得微不足道。不仅实现和使用更直接,iframe内容也是«parent»虚拟dom的实际子节点,这意味着共享事件系统,上下文等。棒极了吧?

import React, { Component } from 'react'
import { createPortal } from 'react-dom'

export default class Frame extends Component {
  constructor(props) {
    super(props)

    this.setContentRef = node =>
      (this.contentRef =
        ((!node || !node.contentWindow) && null) ||
        node.contentWindow.document.body)
  }

  render() {
    const { children, ...props } = this.props // eslint-disable-line
    return (
      <iframe {...props} ref={this.setContentRef}>
        {this.contentRef &&
          createPortal(
            React.Children.only(children),
            this.contentRef
          )}
      </iframe>
    )
  }
}

使用React Hooks时,它变得更加简洁:

import React, { useState } from 'react'
import { createPortal } from 'react-dom'

export const IFrame = ({ children, ...props }) => {
  const [contentRef, setContentRef] = useState(null)
  const mountNode = contentRef && contentRef.contentWindow.document.body

  return (
    <iframe {...props} ref={setContentRef}>
      {mountNode &&
        createPortal(
          React.Children.only(children),
          mountNode
        )}
    </iframe>
  )
}

用法:

import Frame from './Frame'

const MyComp = () => <Frame><h1>Hello Content!</h1></Frame>

this Gist显示,可以轻松实现对iframe <head>的进一步控制。

还有react-frame-component,一个包,恕我直言提供了在React中使用iframe时所需的一切。

<强> SSR

有一件事(至少据我所知),你几乎无法用Portal实现,是服务器端渲染,因为当它们具有对实际DOM节点的引用时,它们只能被渲染。 所以,如果你...

...然后你可以从这样的事情开始:

import React, { Component } from 'react'
import { renderToString } from 'react-dom/server'
import { hydrate, render } from 'react-dom'   

const wrapWithMountNode = html => {
  return `<!DOCTYPE html><html><head></head><body><div id="frame">${html}</div></body></html>`.trim()
}

export default class SSRFrame extends Component {
  constructor(props) {
    super(props)
    this.initialMarkup = wrapWithMountNode(
      renderToString(
        React.Children.only(this.props.children)
      )
    )

    this.contentRef = null
    this.setContentRef = node => {
      this.contentRef =
        ((!node || !node.contentWindow) && null) ||
        node.contentWindow.document.getElementById('frame')
    }
  }

  componentDidMount() {
    this.contentRef &&
      hydrate(
        React.Children.only(this.props.children),
        this.contentRef
      )
  }

  componentDidUpdate() {
    this.contentRef &&
      render(
        React.Children.only(this.props.children),
        this.contentRef
      )
  }

  componentWillUnmount() {
    this.contentRef = null
  }

  render() {
    const { children, ...props } = this.props // eslint-disable-line
    return (
      <iframe
        {...props}
        ref={this.setContentRef}
        srcDoc={
          (!this.contentRef && this.initialMarkup) ||
          undefined
        }
      />
    )
  }
}

2018年的快乐框架!

原帖

就IFrame而言,解决方案实际上非常简单:您必须为IFrame内容创建一个新的DOM渲染器,并将其与应用程序的其余部分同步。这样的组件看起来像这样:

var React =  require('react');
var ReactDOM = require('react-dom');

var IFrame = React.createClass({

    propTypes = {
        frameProps: React.PropTypes.object,
    },

    defaultProps = {
        frameProps: {
            frameBorder: 0
        }
    },

    updateIFrameContents: function() {
        ReactDOM.render((
            // Here you put the components that should be in the IFrame
            <div {...this.props}>Hello IFrame!</div>
        ), this.el);
    },

    render: function() {
        return (
            <iframe {...this.props.frameProps} />
        );
    },

    componentDidMount: function() {
        var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
            el = document.createElement('div');
            frameBody.appendChild(el);
        this.el = el;
        this.updateIFrameContents();
    },

    componentDidUpdate: function() {
        this.updateIFrameContents();
    }
});

现在,这不是非常适合作曲的。你不能使用React.props.children.only之类的东西,因为它们总是指向已经编译/创建的元素,这些元素已经是diff树的一部分。而且因为我们想要一个新的框架内容的差异树,你必须为每个框架内容定义一个新的组件。

输入Higher Order Components。目标是创建一种装饰器,可以应用于您想要框架的任何元素:

function FramedComponent(Component) {
    return React.createClass({

        propTypes = {
            frameProps: React.PropTypes.object,
        },

        defaultProps = {
            frameProps: {
                frameBorder: 0
            }
        },

        updateIFrameContents: function() {
            ReactDOM.render((
                <Component {...this.props} />
            ), this.el);
        },

        render: function() {
            return (
                <iframe {...this.props.frameProps} />
            );
        },

        componentDidMount: function() {
            var frameBody = ReactDOM.findDOMNode(this).contentDocument.body,
                el = document.createElement('div');
                frameBody.appendChild(el);
            this.el = el;
            this.updateIFrameContents();
        },

        componentDidUpdate: function() {
            this.updateIFrameContents();
        }
    });
}

像这样使用:

var MyFramedComponent = FramedComponent(MyComponent);

编辑3-7-2017 React可能是所有其他当前流行的虚拟DOM库的主要优点之一是它的合成事件系统。良好的法律,它只是工作和泡沫非常方便。

通过这种方法,你(非常有意)从下一个中删除一个diff树,对于事件系统也是如此。对于大多数没有太大影响的事件:

var logNestedClicks = function(event) {
    console.log(event);
}
// and then
<MyFramedComponent onClick={logNestedClicks) />

这样可以正常工作。但是有一些不那么突出的例外,特别是考虑到React中的受控iFrame通常不仅仅用于创建范围这一事实,只是没有按预期工作。一个又一个不那么突出的例子:onBeforeInsert。这使得范围Draft实例成为一项非常繁琐的任务。然后,这可能与大多数用例无关。在React中为iFrames制作(WYSIWYG)案例之前,请确保以您期望的方式捕获您的狗屎。去过那里,完成了,believe me

答案 1 :(得分:2)

如果有人只想在iframe中显示小的HTML,则有一个更简单的解决方案。

<iframe src={"data:text/html,"+encodeURIComponent(content)}/>

内容的最大长度为32768个字符。

在接受的答案中也提到了易于使用的react-frame-component软件包。

答案 2 :(得分:1)

您可以使用iframe的srcdoc属性。它将起作用!

  

srcdoc:要嵌入的内联HTML,覆盖src属性。

阅读:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

答案 3 :(得分:0)

这也有效(IE不支持)。

const myHTML = <h1>Hello World</h1>
<iframe srcDoc={myHTML} />

此处有更多信息:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

答案 4 :(得分:0)

使用DOMParser constructor's parseFromString来解析html比接受的答案要简单一些。这是一个示例,其中从DOMParser的生成文档中检索已解析的html。如果您要向iframe发送元素,请忽略.body.innerText的{​​{1}}部分。

parseHtml