在React中加载第三方iframe,使其在页面加载后进行加载,以避免iframe影响PageSpeed得分。

9

我有一个加载第三方小部件的 iframe,我只想在页面加载完毕后显示它,因为我不想影响页面加载速度。我按照一篇medium文章描述的方法操作,但是他们的解决方案无法正常运行,因为 onload 函数 finishLoading 从未被调用

export default ({src, width, height}) => {

  const [loading, stillLoading] = useState(true)
  const finishLoading = () => {
      alert('finished loading')
      stillLoading(false)
  }
  ...
  return(
    {loading ? '' : 
      <iframe
        src={src}
        width={width}
        height={height}
        scrolling="no"
        onLoad={finishLoading}
      >
        className={`tpwidget flex-center-row`}>
      </iframe>
    }
  )
}

更新

通过使用useEffect,我可以让iframe在其他内容之后加载(理论上),但我发现完全删除iframe可以提高我的PageSpeed得分,并且仅仅在之后加载iframe(使用useEffect)对PageSpeed的积极影响不大。


如果有帮助的话,该网站域名是suddenlysask.com,第三方小部件是亚马逊广告。


我使用 https://www.webpagetest.org/result/200725_9B_475e21a5ad8ef0366d10fe6e42d4a5f8/ 检测了您的网站速度,它具有A级首字节时间,因此您的页面速度是完美的,并且您只有一些静态内容缓存问题。我还使用lighthouse进行了检查 https://lighthouse-dot-webdotdevsite.appspot.com//lh/html?url=https%3A%2F%2Fsuddenlysask.com%2F ,由于JavaScript获取和执行时间的数量问题,您存在一些性能问题。请查看这两份报告,尽可能多地修复,并且您可能需要进行代码拆分。 - Ahmed Mokhtar
@AhmedMokhtar 这个问题上有一个看起来非常有用的答案,但我还没有测试过它。这个答案谈到了代码拆分,它是你的吗?如果是你的话,而你将其删除了,能否重新发布一下? - Sam
@Sam 是的,那是我的代码,我会重新发布它。如果您使用代码分离功能,您需要弄清楚需要拆分什么内容。无论您要拆分什么,它都不会包含在初始捆绑中,只有在应用程序需要时才会被获取。 - Ahmed Mokhtar
@Sam,请检查我的答案,尤其是下面的评论。 - Ahmed Mokhtar
显示剩余2条评论
6个回答

7

更新

我访问了该网站,我确定您正在使用 Gatsby。Gatsby 使用 SSR,而 React.lazy 和 Suspense 尚未支持服务器端渲染。如果您想在服务器呈现的应用程序中进行代码拆分,则建议使用 Loadable Components,它在React文档中有一个很好的指南,用于捆绑拆分和服务器端渲染

有一个 Gatsby 插件可以使您的生活更轻松:gatsby-plugin-loadable-components-ssr。安装并配置插件后,您可以像这样使用loadable

AmazonFrame.js

import React from "react";

const AmazonFrame = ({ src, width, height }) => (
  <iframe src={src} width={width} height={height} scrolling="no"></iframe>
);

App.js

import React from "react";
import loadable from "@loadable/component";

const AmazonFrame = loadable(() => import("./AmazonFrame"), {
  fallback: <div>Loading...</div>
});

function App() {
  return (
    <div>
      <AmazonFrame src="src" width="100%" height="200px" />
    </div>
  );
}

export default App;

或者

import React from "react";
import loadable from "@loadable/component";

const AmazonFrame = loadable(() => import("./AmazonFrame"));

function App() {
  return (
    <div>
      <AmazonFrame fallback={<div>Loading...</div>} />
    </div>
  );
}

export default App;

您需要使用 代码分割。 代码分割是一种由打包工具(如Webpack,Rollup和Browserify(通过factor-bundle))支持的功能,它可以创建多个包,这些包可以在运行时动态加载。
如果您正在使用Create React App,则已为您配置此功能,您可以立即开始使用它。
将应用程序进行代码分割可以帮助您“延迟加载”用户当前需要的内容,从而显着提高应用程序的性能。虽然您没有减少应用程序中的总代码量,但您已避免了加载用户可能永远不需要的代码,并减少了初始加载期间所需的代码量。
以下是解决您问题的示例解决方案,它将延迟加载Amazon广告iframe,因此它不会随您的初始捆绑包一起加载:

AmazonFrame.js

import React from "react";

const AmazonFrame = ({ src, width, height }) => (
  <iframe src={src} width={width} height={height} scrolling="no"></iframe>
);

export default AmazonFrame;

App.js

import React, { Suspense, lazy } from "react";

// React.lazy takes a function that must call a dynamic import(). This must return a Promise
// which resolves to a module with a default export containing a React component.
const AmazonFrame = lazy(() => import("./AmazonFrame"));
function App() {
  return (
    <div>
      {/* The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback
       content (such as a loading indicator) while we’re waiting for the lazy component to load */}
      {/* The fallback prop accepts any React elements that you want to render while waiting for the component to load */}
      <Suspense fallback={<div>Loading...</div>}>
        <AmazonFrame src="src" width="100%" height="200px" />
      </Suspense>
    </div>
  );
}

export default App;

请注意,iframe 本身并不是问题,它只是一个单独的元素。问题在于页面内嵌的 iframe 所获取的内容。Lighthouse 的主要投诉是关于两个文件的https://d33wubrfki0l68.cloudfront.net/bundles/8ae87888184e006ea674f2b6393557e56a54c45a.js,我认为这是您的主要捆绑包,因此您可能需要对页面进行代码拆分,以便仅将用户请求的页面包含在初始捆绑包中。 - Ahmed Mokhtar

4

Google PageSpeed/Lighthouse不仅在评估网站性能时分析从服务器发送的静态内容。由于这里演示的大多数技术尽可能快地加载iframe(即在调用useEffect hook时),您会在页面速度测量时间段内发生CPU和网络请求。

以下是几种减轻iframe对得分影响的方法。事先很难确定它的影响,因为它取决于连接/网络和服务器性能 - 您需要测试和实验以获得最佳结果。

  1. Use loading="lazy" on the iframe. This will instruct the browser not to load it until it gets near the viewport (e.g. if it is below the fold it won't load at all until the user scrolls down).
  2. Set a timeout in useEffect to delay loading of the iframe by a fixed duration, reducing the likelihood that it will occur during your page-speed measurement window. E.g.:
    const [ready, setReady] = useState(false)
    useEffect(() => { setTimeout(() => { setReady(true) }, 3000) }, [])
    return (<iframe src={ready ? "/your/url" : "about:blank"} loading="lazy" />)
    
  3. Similar to #2, only listen for an interaction event (e.g. scroll or click) on the page and only load the ads once that event has occurred.

属性'loading'在类型'DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement>'上不存在。ts(2322) - Sam
@Sam 我不确定您需要做什么才能让TypeScript意识到它,但从caniuseMDN中可以看出,它已经存在并获得了浏览器支持。 - coreyward
2
我深入研究后发现,DefinitelyTyped没有这个标准的定义,因此我已经发起了一个拉取请求来添加它。 (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/46434) - coreyward

2
< p > iframe无法加载!< /p > < p > React不会首先挂载iframe,因为它是有条件挂载的(< code > loading < /code >),并且条件永远不会改变,因为它依赖于iframe被挂载。< /p > < p > 您可以在React之外加载iframe,并在< code > useEffect < /code >钩子中附加它:< /p >
export default ({src, width, height}) => {

    const container = useRef()
    useEffect(() => {
        const frm = document.createElement('iframe')
        frm.src = src
        frm.width = width
        frm.height = height
        frm.scrolling = 'no'
        container.current.appendChild(frm)
    }, [])

    return(
        <div ref={container} />
    )
}

话虽如此,我无法告诉您这是否会加快页面加载速度,毕竟,iframes会加载外部内容。但是您可以尝试一下。


是的,AskMen提供的解决方案指出我可以使用useEffect在页面加载后加载iframe,但我发现如果我完全删除iFrame,页面速度会更快。 - Sam
不,AskMen的解决方案并没有在效果中加载iframe,它只是错误地标记加载已完成,然后实际上在渲染时开始加载。 - Mordechai
1
由于iFrame将另一个网页嵌入到网站中,因此您最终会获得更快的加载速度。这意味着它需要获取内容并嵌入其中,这总是会影响加载速度。使用useEffect钩子在组件挂载后加载iFrame可能是最好的解决方法。虽然如果您不需要在页面加载时传递iFrame(如果它不在视口中),您可以尝试将其推迟到屏幕外,并抓取useOnScreen自定义钩子以在它变为视口中可见时加载它。虽然我不能百分之百确定这会帮助提高加载速度。 - Oliver Heward
1
@OliverHeward 你应该发布一个解决方案 - Sam
我想翻译,但我相信自从这个问题被问出来以后,已经有更好的答案被发布了 :) - Oliver Heward

1

最近添加了一个新属性,可以提高iframe的性能。它被称为loading,它可以有2个值,表示浏览器应该如何加载iframe:eager(立即加载iframe)和lazy(延迟加载iframe,直到它到达视口的计算距离)。因此,它将是这样的:<iframe src={src} loading="eager"/> 更多信息请阅读此处


0

你必须使用 useEffect() 钩子。

useEffect(() => {
  stillLoading(false)
}, [])

在这种情况下,stillLoading(false) 只会在组件挂载后应用。因此,在页面加载完成后,您的条件才能起作用。

好的,我不知道为什么我没有想到这个。 - Sam
所以我已经这样做了,但是我的第三方iframe仍然会大大降低我的页面加载时间。我可能只能放弃iframe,因为那个广告给我带来了一些页面速度洞察的警告,例如文本压缩、未使用的JavaScript、适当大小的图像、高效编码的图像等等。也许这些警告无论如何都会发生?我认为如果我删除广告,我的页面速度评分会提高20分,但如果有办法让它不影响我的页面速度评分,我想保留它。 - Sam
1
这只是在下一个渲染周期中呈现和加载iframe。它不会在后台加载它。 - Mordechai
这是正确的答案,因为应用程序由gatsby进行服务器端渲染,并且当gatsby构建页面时,useEffect不会在初始渲染时运行,因此它将防止iframe被渲染。 - Ahmed Mokhtar

0
你可以使用 window.onload 函数在运行时加载 iframe。
function loadDeferredIframe() {
    // this function will load the Google homepage into the iframe
    var iframe = document.getElementById("my-deferred-iframe");
    iframe.src = "./" // here goes your url
};
window.onload = loadDeferredIframe;

首先,您可以拥有一个指向空白的 iframe 元素,并且可以在运行时更改 src。

<iframe id="my-deferred-iframe" src="about:blank" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接