You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React:如何判断dangerouslySetInnerHTML注入内容加载完成?

判断dangerouslySetInnerHTML中第三方嵌入内容的加载完成状态

我用React的dangerouslySetInnerHTML嵌入推文、Reddit帖子这类第三方内容,代码如下:

<div dangerouslySetInnerHTML={{__html: html}}/>

其中html包含第三方提供的嵌入代码(比如Twitter的示例):

<blockquote class="twitter-tweet" > 
  <p lang="en" dir="ltr"> I’m Bowser!!!<a href="...">@supermariomovie</a> <a href="...">pic.twitter.com/wr9KRX9syE</a></p>&mdash; Jack Black (@jackblack) <a href="...">October 11, 2022</a> 
</blockquote> 
<script async src="..." charset="utf-8"></script>

想知道怎么判断这些嵌入内容何时加载完成,以便在加载期间显示进度圈。


针对特定平台的精准方案

主流第三方平台大多会提供加载回调或事件,能精准判断内容加载完成:

Twitter 推文

Twitter的嵌入脚本会暴露全局twttr对象,可通过它的ready事件或events监听加载状态:

import { useEffect, useState } from 'react';

function EmbedTweet({ html }) {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    const handleLoad = () => setLoading(false);

    // 处理twttr已加载的情况
    if (window.twttr) {
      twttr.ready(handleLoad);
      // 监听单个推文加载完成(可选)
      twttr.events.bind('tweet', handleLoad);
    } else {
      // 手动加载Twitter脚本并监听onload
      const script = document.createElement('script');
      script.src = 'https://platform.twitter.com/widgets.js';
      script.onload = handleLoad;
      document.body.appendChild(script);
    }

    // 清理事件,避免内存泄漏
    return () => {
      if (window.twttr) {
        twttr.events.unbind('tweet', handleLoad);
      }
    };
  }, [html]);

  return (
    <>
      {loading && <div className="spinner">加载中...</div>}
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </>
  );
}

Reddit 帖子

Reddit的嵌入脚本加载完成后会触发reddit-embed-ready全局事件,也可直接监听脚本的onload:

import { useEffect, useState } from 'react';

function EmbedReddit({ html }) {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    const handleLoad = () => setLoading(false);

    // 检查是否已存在Reddit嵌入脚本
    const existingScript = document.querySelector('script[src*="reddit.com/embed.js"]');
    if (existingScript) {
      existingScript.onload = handleLoad;
    } else {
      const script = document.createElement('script');
      script.src = 'https://www.reddit.com/embed.js';
      script.onload = handleLoad;
      document.body.appendChild(script);
    }

    // 监听全局事件作为备选
    window.addEventListener('reddit-embed-ready', handleLoad);
    return () => window.removeEventListener('reddit-embed-ready', handleLoad);
  }, [html]);

  return (
    <>
      {loading && <div className="spinner">加载中...</div>}
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </>
  );
}

通用方案(适配多数第三方嵌入)

如果平台没有提供明确的回调,可通过以下两种方式判断:

1. 监听DOM变化(MutationObserver)

用MutationObserver观察嵌入容器的子元素变化,当一段时间内没有新元素添加时,认为加载完成:

import { useEffect, useState } from 'react';

function EmbedContent({ html }) {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    const container = document.getElementById('embed-container');
    let timeoutId;

    const observer = new MutationObserver(() => {
      // 每次DOM变化后重置定时器,等待1秒无变化则停止加载状态
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        setLoading(false);
        observer.disconnect();
      }, 1000);
    });

    // 监听容器的所有子元素变化(包括后代)
    observer.observe(container, { childList: true, subtree: true });

    // 清理定时器和观察者
    return () => {
      clearTimeout(timeoutId);
      observer.disconnect();
    };
  }, [html]);

  return (
    <>
      {loading && <div className="spinner">加载中...</div>}
      <div 
        id="embed-container"
        dangerouslySetInnerHTML={{ __html: html }}
      />
    </>
  );
}

注:定时器时长可根据嵌入内容的加载速度调整,比如500ms或2000ms。

2. 统计资源加载完成数

解析嵌入内容中的图片、脚本、样式等资源,监听每个资源的加载状态,全部加载(或失败)后结束加载状态:

import { useEffect, useState } from 'react';

function EmbedContent({ html }) {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    
    // 提取所有资源URL
    const resources = [...doc.querySelectorAll('img, script, link[rel="stylesheet"]')]
      .map(el => el.src || el.href)
      .filter(url => url); // 过滤空URL

    let loadedCount = 0;
    const total = resources.length;

    const handleResourceLoad = () => {
      loadedCount++;
      if (loadedCount === total) {
        setLoading(false);
      }
    };

    // 逐个加载资源并监听状态
    resources.forEach(url => {
      if (url.endsWith('.js')) {
        const script = document.createElement('script');
        script.src = url;
        script.onload = handleResourceLoad;
        script.onerror = handleResourceLoad; // 加载失败也算完成
        document.body.appendChild(script);
      } else if (url.endsWith('.css')) {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = url;
        link.onload = handleResourceLoad;
        link.onerror = handleResourceLoad;
        document.head.appendChild(link);
      } else {
        const img = new Image();
        img.src = url;
        img.onload = handleResourceLoad;
        img.onerror = handleResourceLoad;
      }
    });

    // 没有资源时直接结束加载状态
    if (total === 0) {
      setLoading(false);
    }
  }, [html]);

  return (
    <>
      {loading && <div className="spinner">加载中...</div>}
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </>
  );
}

注意事项

  • 第三方脚本可能是异步加载的,需确保监听逻辑在脚本加载前执行,避免错过事件。
  • 通用方案无法做到100%精准,需根据实际场景调整参数(比如定时器时长)。
  • 建议添加超时逻辑,比如加载超过10秒后强制结束加载状态,避免加载圈一直显示。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 14:20:38