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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34743264/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 16:48:47  来源:igfitidea点击:

How to set iframe content of a react component

javascriptiframereactjsxmlhttprequest

提问by user3807940

I am trying to set the content of an iframe in a React component but I am not able to do it. I have a component in which contains a function which has to be called when the iframe finishes loading. In that function i am setting the content but it doesnt seem like the onload function is called at all. I am testing it in chrome browser. I am trying the following:

我正在尝试在 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;

What am i doing wrong?

我究竟做错了什么?

回答by Lukas Bünger

Edit 10-25-2018

编辑 10-25-2018

This whole frame stuff became actually trivial with the introduction of Portals in React 16. Not only is the implementation and usage much more straightforward, the iframe contents are also actual children of the ?parent? virtual dom, which means shared event system, contexts and so on. Awesome, right?

随着React 16Portals的引入,整个框架的内容实际上变得微不足道。不仅实现和使用更加简单,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>
    )
  }
}

It gets even more terse when using React Hooks:

使用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>
  )
}

Usage:

用法:

import Frame from './Frame'

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

Further control, for example over the iframes <head>, can easily be achieved as this Gistshows.

<head>本要点所示,可以轻松实现进一步控制,例如对 iframes 的控制。

There is also react-frame-component, a package that IMHO offers pretty much everything you need when working with iframes in React.

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

SSR

固态继电器

One thing that (at least to my knowledge) you'll hardly ever get right with Portals though, is server side rendering, because Portals are only ever renderable, when they have a reference to an actual DOM node. So if you ...

一件事(至少据我所知)您几乎无法正确使用 Portals,但是服务器端渲染,因为 Portals 只有在它们具有对实际 DOM 节点的引用时才可渲染。所以如果你...

... then you could start from something like this:

......那么你可以从这样的事情开始:

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
        }
      />
    )
  }
}

Happy framing in 2018!

2018 年框架快乐!

Original Post

原帖

As far as the IFrame is concerned, the solution is actually very simple: You have to create a new DOM renderer for the IFrame content and sync it with the rest of the app. Such a component could look something like this:

就 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();
    }
});

Now, this is not very composition-friendly. You can't use React.props.children.onlyand the likes, because these always point at already compiled/created elements that are already part of the diff tree. And because we want a new diff tree for framed contents, you'd have to define a new component for every framed content.

现在,这不是很适合组合。您不能使用React.props.children.only等,因为这些总是指向已经编译/创建的已经是差异树一部分的元素。因为我们想要一个新的框架内容差异树,你必须为每个框架内容定义一个新组件。

Enter Higher Order Components. The goal is to create kind of a decorator that can be applied to any element you want to have framed:

输入高阶组件。目标是创建一种可以应用于您想要框架的任何元素的装饰器:

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();
        }
    });
}

Use like this:

像这样使用:

var MyFramedComponent = FramedComponent(MyComponent);

Edit 3-7-2017One of the main advantages of React over probably all the other currently en vogue virtual DOM libraries, is its synthetic event system. Good lawd, it just works and bubbles so very conveniently.

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

With this approach though, you'll (quite intentionally) cut one diff tree from the next one and that is true for the event system as well. For most events that doesn't make that much of a difference:

但是,使用这种方法,您将(非常有意地)从下一个差异树中切割出一个差异树,这对于事件系统也是如此。对于大多数没有太大区别的事件:

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

This will just work fine. But there are some not-so-prominent exceptions, that, especially given the fact that controlled iFrames in React are more often than not used to merely create scope, are just not working as expected. A again not-so-prominent example: onBeforeInsert. Which renders scoping Draftinstances a very tedious task. And then again, this is probably irrelevant for most use cases. Just make sure your shit gets captured the way you'd expect before making a (WYSIWYG) case for iFrames in React. Been there, done that, believe me.

这将正常工作。但是也有一些不那么突出的例外,尤其是考虑到 React 中受控 iFrame 经常仅用于创建作用域这一事实,它们无法按预期工作。再一个不那么突出的例子:onBeforeInsert. 这使得范围草案实例成为一项非常乏味的任务。再说一次,这可能与大多数用例无关。在为 React 中的 iFrame 制作(WYSIWYG)案例之前,请确保您的狗屎以您期望的方式被捕获。去过那里,做到了,相信我

回答by Jakub Zawi?lak

There is an easier solution if someone just wants to display small HTML inside the iframe.

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

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

The max length of content is 32768 characters.

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

There is also easy to use react-frame-componentpackage which was mentioned in the accepted answer.

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

回答by Kaleem Elahi

You can use srcdocattribute of iframe. It will work!

您可以使用srcdociframe 的属性。它会起作用!

srcdoc: Inline HTML to embed, overriding the src attribute.

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

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

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

回答by NathanQ

Using the DOMParser constructor'sparseFromString to parse the html is a little simpler than the accepted answer. Here is an example where the parsed html is retrieved from the DOMParser's generated document. If you're sending an element to the iframe, leave out the .body.innerTextpart of the parseHtml.

使用DOMParser 构造函数的parseFromString 来解析 html 比接受的答案简单一点。这是一个示例,其中从 DOMParser 生成的文档中检索已解析的 html。如果你发送一个元素的iframe,离开了.body.innerText的部分parseHtml

class SimpleIframe extends Component {
    render() {
        const parseHtml = html => new DOMParser().parseFromString(html, 'text/html').body.innerText;
        return <iframe srcDoc={parseHtml(this.props.wholeHTMLDocumentString)} />;
    }
}

回答by Sam Walpole

This works too (not supported in IE).

这也有效(IE 不支持)。

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

More info here: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe

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