Files
astro-jiao77.cn/docs/CHART_JITTER_FIX.md
2025-10-01 16:05:43 +08:00

7.8 KiB
Raw Blame History

模态框图表抽搐问题修复

📅 修复日期

2025年10月1日

🐛 问题描述

症状

在点击EDA学术发表指南的场所卡片时弹出的模态框中的图表会出现"抽搐"现象:

  • 图表在渲染时会突然变大或变小
  • 图表尺寸会快速闪烁调整
  • 视觉体验不流畅

根本原因

时序问题:图表在模态框动画进行中就开始渲染,而此时容器的尺寸还在变化。

问题流程(修复前)

1. 点击卡片
2. 立即渲染图表(容器尺寸 = 卡片大小)
3. GSAP动画开始
4. 容器从卡片大小 → 模态框大小0.5秒动画)
5. 图表被迫在动画中重新计算尺寸
6. 结果:图表"抽搐"

问题代码

// ❌ 错误:图表在动画之前渲染
modalContent.innerHTML = `...`;

// 渲染图表(此时容器尺寸还在变化)
const ctx = canvas.getContext('2d');
activeChart = new Chart(ctx, {...});

// GSAP动画容器尺寸正在改变
const tl = gsap.timeline({
  onComplete: () => { isAnimating = false; }
});
tl.to(modal, {
  width: targetW,   // 容器宽度变化
  height: targetH,  // 容器高度变化
  duration: 0.5
});

修复方案

核心思路

延迟图表渲染:等待模态框动画完成后再渲染图表,确保容器尺寸已经稳定。

修复后的流程

1. 点击卡片
2. 设置模态框内容(但不渲染图表)
3. GSAP动画开始
4. 容器从卡片大小 → 模态框大小0.5秒动画)
5. 动画完成回调触发
6. 渲染图表(容器尺寸已稳定)
7. 结果:图表平滑出现,无抽搐

🔧 技术实现

1. 提取图表渲染为独立函数

之前

// 图表渲染代码直接嵌入在 openModal 中
try {
  const Chart = (window as any).Chart;
  const ctx = canvas.getContext('2d');
  activeChart = new Chart(ctx, {...});
} catch (err) {
  console.warn('绘制图表时出错:', err);
}

现在

/**
 * 渲染图表(独立函数,在动画完成后调用)
 */
function renderVenueChart(venue: VenueData): void {
  try {
    const Chart = (window as any).Chart;
    if (Chart && venue.acceptanceValue !== null) {
      const canvas = document.getElementById('venueChart') as HTMLCanvasElement;
      if (!canvas) return;
      
      const ctx = canvas.getContext('2d');
      if (ctx) {
        // 使用 requestAnimationFrame 确保画布尺寸已稳定
        requestAnimationFrame(() => {
          activeChart = new Chart(ctx, {
            type: 'bar',
            data: {...},
            options: {
              responsive: true,
              maintainAspectRatio: false,
              ...
            }
          });
        });
      }
    }
  } catch (err) {
    console.warn('绘制图表时出错:', err);
  }
}

2. 在动画完成回调中调用

之前

const tl = gsap.timeline({
  onComplete: () => { 
    isAnimating = false; 
  }
});

现在

const tl = gsap.timeline({
  onComplete: () => { 
    isAnimating = false;
    // 动画完成后渲染图表
    renderVenueChart(venue);
  }
});

3. 添加 requestAnimationFrame 保护

requestAnimationFrame(() => {
  activeChart = new Chart(ctx, {...});
});

作用

  • 确保在浏览器下一帧渲染前执行
  • 给容器一个重绘的机会
  • 确保尺寸计算准确

4. 简化模式同步修复

function openSimpleModal(venueId: string): void {
  // ... 设置内容 ...
  
  // 简化模式下也渲染图表
  renderVenueChart(venue);
}

📊 时序对比

修复前的时序

时间轴:
0ms    - 点击卡片
0ms    - 渲染图表(容器 = 300x200
10ms   - GSAP动画开始
10ms   - 容器尺寸变化开始
100ms  - 图表检测到容器变化,重新计算
200ms  - 容器继续变化
300ms  - 图表再次调整(抽搐)
500ms  - 动画结束
510ms  - 图表最终稳定

修复后的时序

时间轴:
0ms    - 点击卡片
0ms    - 设置内容(图表占位符)
10ms   - GSAP动画开始
10ms   - 容器尺寸变化
500ms  - 动画完成
500ms  - onComplete 回调
510ms  - renderVenueChart 调用
520ms  - requestAnimationFrame
530ms  - 图表渲染(容器尺寸稳定 = 800x600
540ms  - 图表平滑出现 ✅

🎯 关键优化点

1. 延迟渲染

  • 等待容器尺寸稳定
  • 避免动画中的重新计算

2. requestAnimationFrame

  • 浏览器下一帧渲染
  • 确保DOM已更新
  • 尺寸计算准确

3. 独立函数

  • 代码复用GSAP模式 + 简化模式)
  • 逻辑清晰
  • 易于维护

4. 错误处理

  • try-catch 包裹
  • 优雅降级
  • 控制台警告

📝 修改文件

文件: /src/pages/report/ai-eda-paper-report/eda-venues-interactive.ts

修改内容:

  1. 新增 renderVenueChart() 函数
  2. 修改 openModal() - 将图表渲染移到 onComplete
  3. 修改 openSimpleModal() - 调用新的图表渲染函数

代码行数: ~35行修改

🚀 构建验证

0 errors, 0 warnings
✓ 20 pages built
✓ 图表抽搐问题已修复

🎨 用户体验提升

修复前

  • 图表闪烁、跳动
  • 尺寸不稳定
  • 视觉体验差
  • 看起来像bug

修复后

  • 图表平滑出现
  • 尺寸稳定
  • 视觉流畅
  • 专业感强

💡 技术要点

Chart.js 响应式原理

options: {
  responsive: true,           // 响应容器尺寸变化
  maintainAspectRatio: false  // 不保持宽高比,填充容器
}

问题当容器尺寸变化时Chart.js 会自动调整图表尺寸 解决:等待容器尺寸稳定后再渲染图表

GSAP Timeline onComplete

const tl = gsap.timeline({
  onComplete: () => {
    // 所有动画完成后执行
    renderVenueChart(venue);
  }
});

优势

  • 精确的时序控制
  • 保证动画完成
  • 避免中途干扰

requestAnimationFrame 的作用

requestAnimationFrame(() => {
  // 浏览器下一帧执行
  activeChart = new Chart(ctx, {...});
});

原理

  1. 等待浏览器重绘
  2. 确保DOM已更新
  3. 尺寸计算准确
  4. 避免布局抖动

📚 相关知识

1. Chart.js 尺寸计算

  • Chart.js 基于父容器的 clientWidthclientHeight
  • 动画中这些值会不断变化
  • 导致 Chart.js 多次重新计算和渲染

2. GSAP 动画时序

  • duration: 0.5 = 500ms
  • ease: 'expo.out' = 先快后慢
  • onComplete = 动画完成回调

3. 浏览器渲染流程

JavaScript → Style → Layout → Paint → Composite
                      ↑
              requestAnimationFrame 在此执行

🎯 最佳实践

处理动画中的图表渲染

  1. 延迟渲染 - 等待容器尺寸稳定
  2. 使用回调 - onComplete、onUpdate 等
  3. requestAnimationFrame - 确保DOM更新
  4. 销毁旧图表 - 避免内存泄漏

避免的错误

  1. 在动画进行中渲染图表
  2. 不检查容器尺寸
  3. 不使用 requestAnimationFrame
  4. 忘记销毁旧图表实例

🔍 调试技巧

如何验证修复

  1. 打开浏览器开发者工具
  2. 切换到 Performance 标签
  3. 录制点击卡片的过程
  4. 查看渲染时序
  5. 确认图表在动画完成后才渲染

控制台日志

console.log('动画开始');
const tl = gsap.timeline({
  onComplete: () => {
    console.log('动画完成');
    renderVenueChart(venue);
    console.log('图表渲染完成');
  }
});

修复完成! 🎉
现在模态框弹出时图表会平滑出现,无任何抽搐现象。

建议测试

  • 点击不同的场所卡片
  • 观察图表渲染是否平滑
  • 检查控制台无错误

下一步

  • 运行 npm run dev 测试效果
  • 或直接 npm run build && ./deploy-full.sh 部署