我有一个调用
document
全局的组件。当我在 iframe 中渲染此组件时,它使用当前(父)文档作为参考,而不是 iframe 的文档。
export const Modal: FC<ModalProps> = ({ opened, ...props }) => {
// ...
useEffect(() => {
// Prevent background scrolling when the modal is opened.
// This line locks scroll on the main DOM, but I only want it to be blocked on the iframe.
document.body.style.overflow = opened ? "hidden" : "";
}, [opened]);
// ...
}
我可以渲染
Modal
组件,以便 document
引用 iframe 文档(而不是主 DOM)吗?
我使用这个自定义组件在单独的 iframe 中渲染 React 元素:
import { FC, DetailedHTMLProps, IframeHTMLAttributes, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
export const IFrame: FC<DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>> = ({ children, style, ...props }) => {
const [contentRef, setContentRef] = useState<HTMLIFrameElement | null>(null);
const headNode = useMemo(() => contentRef?.contentWindow?.document?.head, [contentRef]);
const mountNode = useMemo(() => contentRef?.contentWindow?.document?.body, [contentRef]);
// Setup head.
useEffect(() => {
if (headNode) {
// Inherit parent styles (like compiled css modules or local fonts).
for (const style of Array.from(document.head.querySelectorAll("link"))) {
headNode.appendChild(style.cloneNode(true));
}
for (const style of Array.from(document.head.querySelectorAll("style"))) {
headNode.appendChild(style.cloneNode(true));
}
}
}, [headNode]);
return (
<>
<iframe
{...props}
style={{ border: "none", ...style }}
ref={setContentRef}
/>
{mountNode ? createPortal(children, mountNode) : null}
</>
);
});
我还尝试创建一个新的根并渲染其中的子级,但它也不起作用。
useEffect(() => {
if (mountNode) {
const root = createRoot(mountNode);
root.render(children);
return () => {
root.unmount();
};
}
}, [mountNode]);
return (
<iframe
{...props}
style={{ border: "none", ...style }}
ref={setContentRef}
/>
);
由于没有其他人尝试过这一点,请原谅这个答案中的一些猜测。
我认为这里发生的是,当您将内容放入 React 的
<iframe>
元素中时,它会使用 srcdoc
属性创建内容,而不是更正常的 src
。这会导致您的 iframe 内容与您的父页面存在于同一 DOM 上下文中。
要解决这个问题,我认为您要么需要强制 React 将新页面加载到您的 iframe 中,要么将您之后的上下文数据存储在此组件内,并将 setState 方法传递给您的子节点。