如何设置 React 组件的 iframe 内容

How to set iframe content of a react component

我想在 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;

我做错了什么?

TLDR;

如果您正在寻找一种通过 React 以 de-facto 规范方式控制 <iframe> 内容的方法,Portals 是您的不二之选。 与 Portal 的所有事物一样:一旦您建立对现有和已安装的 DOM 节点的引用(在这种情况下,它将是给定 <iframe>contentWindow)和 用它创建一个 Portal,它的内容也被认为是 «parent» 虚拟 DOM 的子级,这意味着共享(合成)事件系统、上下文等。

请注意,为了代码简洁,下面的示例使用了 Optional chaining operator, 在撰写本文时,并非所有浏览器都支持它。

示例:一个 功能性 React 组件 包括 hooks:

// iframe.js

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

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

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

示例:A React class 组件:

// iframe.js

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

export class IFrame extends Component {
  constructor(props) {
    super(props)
    this.state = {
      mountNode: null
    }
    this.setContentRef = (contentRef) => {
      this.setState({
        mountNode: contentRef?.contentWindow?.document?.body
      })
    }
  }

  render() {
    const { children, ...props } = this.props
    const { mountNode } = this.state
    return (
      <iframe
        {...props}
        ref={this.setContentRef}
      >
        {mountNode && createPortal(children, mountNode)}
      </iframe>
    )
  }
}

用法:

import { IFrame } from './iframe'

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

进一步控制,例如 <iframe>s <head> 内容,可以很容易地实现为 this Gist 显示。

还有react-frame-component,恕我直言,提供的套餐非常多 在 React 中使用受控 <iframe>s 时所需的一切。

注意事项:

  • 此答案仅针对给定 <iframe> 的所有者想要以 React-ish 方式以编程方式控制(如决定)其内容的用例。
  • 此答案假定 <iframe> 的所有者遵守 Same-origin policy
  • 此答案不适合跟踪在 <iframe src="https://www.openpgp.org/> 种情况下如何以及何时加载外部资源。
  • 如果您关心可访问性,则应该提供 iframe meaningful title attributes

用例(我知道的);

  • OP 的用例:广告以及控制广告如何以及何时可以访问您网站上安全范围内的元素的需要。
  • 可嵌入 third-party 个小部件。
  • 我的用例(以及因此我对此事的一些知情立场):CMS UI,您希望用户能够在其中预览范围 CSS 样式,包括应用的媒体查询。

将一组给定的 CSS 样式(或样式表)添加到受控的 <iframe>:

正如一位评论作者所指出的,在父应用程序和受控 <iframe> 的内容之间管理样式可能非常棘手。 如果您足够幸运,拥有 (a) 专用 CSS 文件,其中包含您的 <iframe> 所有必要的视觉说明,可能就足够了 将引用所述样式的 <link> 标签传递给你的 IFrame 组件,即使这不是最符合标准的方式来处理 <link>refs:

const MyComp = () => (
  <Frame>
    <link rel="stylesheet" href="my-bundle.css">
    <h1>Hello Content!</h1>
  </Frame>
) 

然而,在当今时代,尤其是在 React 世界中,大多数时候,构建设置会即时创建样式和样式表: 因为他们利用 meta-languages 之类的 SASS 或什至更复杂的解决方案,例如 CSS-in-JS 东西 (styled-components, emotion).

此沙箱包含如何将一些更流行的样式策略与 React 中的 iframe 集成的示例。

这个答案过去也提供了关于 16.3 之前的 React 版本的食谱。然而,在这个时候,我认为可以肯定地说,大多数 我们能够推出一个包含 Portals 的 React 版本,并在较小程度上包含 hooks。如果您需要有关 iframe 和 React 版本 < 16 的解决方案, 打我,我很乐意提供建议。

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

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

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

还有易于使用的 react-frame-component 包,已在接受的答案中提到。

这也有效(IE 不支持)。

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

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

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

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

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

使用 DOMParser constructor's parseFromString 解析 html 比接受的答案简单一点。这是一个示例,其中从 DOMParser 生成的文档中检索已解析的 html。如果您要向 iframe 发送元素,请省略 parseHtml.

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