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>— 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
相关产品推荐
相关产品推荐

