From 927e9c29d1340a31d6586fdbae32dd29073da5fb Mon Sep 17 00:00:00 2001 From: nanhaoluo <3075912108@qq.com> Date: Sun, 25 Jan 2026 01:13:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20Mermaid=20?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 12.1 实现批量渲染:使用 requestAnimationFrame 分批渲染,避免阻塞主线程 - 12.2 添加加载动画:显示加载状态和旋转动画,提供视觉反馈 - 12.3 实现延迟渲染:优先渲染视口内图表,延迟渲染视口外图表 - 12.4 优化错误处理:单个图表渲染失败不影响其他图表 需求:18.1, 18.2, 18.3, 18.4, 18.5 --- argontheme.js | 175 +++++++++++++++++++++++++++++++++++++++----------- style.css | 49 ++++++++++++++ 2 files changed, 187 insertions(+), 37 deletions(-) diff --git a/argontheme.js b/argontheme.js index 99be8d0..9c99c62 100644 --- a/argontheme.js +++ b/argontheme.js @@ -5235,6 +5235,8 @@ void 0; /** * 批量渲染所有 Mermaid 图表 + * 需求 18.1: 使用批量渲染,避免阻塞主线程 + * 需求 18.4: 延迟渲染视口外的图表,优先渲染可见图表 */ renderAllCharts() { if (!this.initialized) { @@ -5250,41 +5252,111 @@ void 0; return; } - // 批量渲染 - blocks.forEach((block, index) => { - this.renderChart(block, index); + this.logDebug(`准备批量渲染 ${blocks.length} 个图表`); + + // 需求 18.4: 将图表分为视口内和视口外两组 + const visibleBlocks = []; + const invisibleBlocks = []; + + blocks.forEach(block => { + if (this.isInViewport(block)) { + visibleBlocks.push(block); + } else { + invisibleBlocks.push(block); + } }); - this.logDebug(`完成渲染 ${blocks.length} 个图表`); + this.logDebug(`视口内图表: ${visibleBlocks.length}, 视口外图表: ${invisibleBlocks.length}`); + + // 优先渲染视口内的图表 + let currentIndex = 0; + const batchSize = 3; // 每批渲染 3 个图表 + + const renderBatch = (blockList, isVisible) => { + if (currentIndex >= blockList.length) { + // 当前列表渲染完成 + if (isVisible && invisibleBlocks.length > 0) { + // 视口内图表渲染完成,开始渲染视口外图表 + this.logDebug('视口内图表渲染完成,开始渲染视口外图表'); + currentIndex = 0; + requestAnimationFrame(() => renderBatch(invisibleBlocks, false)); + } else { + this.logDebug(`完成渲染 ${blocks.length} 个图表`); + } + return; + } + + const endIndex = Math.min(currentIndex + batchSize, blockList.length); + + // 渲染当前批次 + for (let i = currentIndex; i < endIndex; i++) { + const globalIndex = blocks.indexOf(blockList[i]); + this.renderChart(blockList[i], globalIndex); + } + + currentIndex = endIndex; + + // 继续下一批 + requestAnimationFrame(() => renderBatch(blockList, isVisible)); + }; + + // 开始渲染(优先渲染视口内图表) + if (visibleBlocks.length > 0) { + requestAnimationFrame(() => renderBatch(visibleBlocks, true)); + } else if (invisibleBlocks.length > 0) { + // 如果没有视口内图表,直接渲染视口外图表 + requestAnimationFrame(() => renderBatch(invisibleBlocks, false)); + } + }, + + /** + * 检查元素是否在视口内 + * @param {HTMLElement} element - 要检查的元素 + * @returns {boolean} 是否在视口内 + */ + isInViewport(element) { + const rect = element.getBoundingClientRect(); + const windowHeight = window.innerHeight || document.documentElement.clientHeight; + const windowWidth = window.innerWidth || document.documentElement.clientWidth; + + // 元素至少部分可见 + return ( + rect.top < windowHeight && + rect.bottom > 0 && + rect.left < windowWidth && + rect.right > 0 + ); }, /** * 渲染单个图表 + * 需求 18.5: 图表渲染失败不阻塞其他图表的渲染 * @param {HTMLElement} element - 代码块元素 * @param {number} index - 图表索引 */ renderChart(element, index) { const chartId = `mermaid-chart-${Date.now()}-${index}`; - // 检查是否已经是错误容器(避免重复处理错误) - if (element.classList && element.classList.contains('mermaid-error-container')) { - this.logDebug(`元素已经是错误容器,跳过: ${chartId}`); - return; - } - - // 检查是否已经是渲染成功的容器(避免重复渲染) - if (element.classList && element.classList.contains('mermaid-container') && element.dataset.mermaidCode) { - this.logDebug(`图表已成功渲染,跳过: ${chartId}`); - return; - } - - // 检查是否已渲染(避免重复渲染) - if (this.rendered.has(element)) { - this.logDebug(`图表已渲染,跳过: ${chartId}`); - return; - } - + // 需求 18.5: 使用 try-catch 包裹整个渲染过程 try { + // 检查是否已经是错误容器(避免重复处理错误) + if (element.classList && element.classList.contains('mermaid-error-container')) { + this.logDebug(`元素已经是错误容器,跳过: ${chartId}`); + return; + } + + // 检查是否已经是渲染成功的容器(避免重复渲染) + if (element.classList && element.classList.contains('mermaid-container') && element.dataset.mermaidCode) { + this.logDebug(`图表已成功渲染,跳过: ${chartId}`); + return; + } + + // 检查是否已渲染(避免重复渲染) + if (this.rendered.has(element)) { + this.logDebug(`图表已渲染,跳过: ${chartId}`); + return; + } + // 提取代码 const code = this.extractMermaidCode(element); @@ -5296,7 +5368,20 @@ void 0; this.logDebug(`准备渲染图表: ${chartId}, 代码长度: ${code.length}`); this.logDebug(`代码内容: ${code.substring(0, 200)}`); - // 创建容器 + // 需求 18.2: 创建加载动画容器 + const loadingContainer = document.createElement('div'); + loadingContainer.className = 'mermaid-loading'; + loadingContainer.innerHTML = ` +
+