問題

在 Nivo custom layer 中使用 SVG <text> 渲染超出 innerWidth / innerHeight 的元素,會擴展 SVG bounding box,觸發 nivo 內部的 resize 偵測,導致無限重新渲染迴圈,頁面凍結。

這類問題很難除錯:Console 不會有錯誤訊息、ErrorBoundary 無法攔截、React DevTools 也來不及載入。

觸發流程

1. CumulativeLineLayer 在 x={innerWidth + 8} 渲染 <text>
2. SVG content bounding box 擴展超出 inner area
3. Nivo 偵測到尺寸變化 → 觸發重新渲染
4. 重新渲染後 bounding box 再次改變 → 回到步驟 3 → 無限迴圈

問題程式碼

<text
  x={innerWidth + 8}   // 超出 inner area → 改變 bounding box
  y={y}
  fontSize="11"
  fill="#718096"
>
  {pct}
</text>

解決方式

使用 <foreignObject> + HTML <div> 取代 SVG <text><foreignObject> 內的 HTML 內容不會影響 SVG bounding box,因此不會觸發 resize 偵測。

<foreignObject
  x={innerWidth + 4}
  y={y - 7}
  width={24}
  height={14}
  pointerEvents="none"
>
  <div style={{ fontSize: 11, lineHeight: '14px', color: '#718096', whiteSpace: 'nowrap' }}>
    {pct}
  </div>
</foreignObject>

開發原則

  1. 不要渲染超出 innerWidth / innerHeight 的 SVG <text> — 會觸發 nivo resize 偵測
  2. 使用 <foreignObject> + HTML 顯示文字標籤 — 不影響 SVG bounding box
  3. 避免在 custom layer 中執行會改變 DOM 的邏輯(如 truncateText() — 不同渲染結果可能觸發 resize 迴圈
  4. 裝飾性覆蓋元素加上 pointerEvents="none" — 避免干擾 nivo 內建互動

相關案例

Sankey 圖表曾遇到相同問題:在 custom layer 中呼叫 truncateText() 改變 SVG 內容,觸發相同的無限迴圈。解法同樣是改用 <foreignObject> + CSS text-overflow: ellipsis

參見 Drawer Form 資料不更新的解決方案 — 另一個 UI 元件狀態不同步的模式。