問題
在 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>開發原則
- 不要渲染超出
innerWidth/innerHeight的 SVG<text>— 會觸發 nivo resize 偵測 - 使用
<foreignObject>+ HTML 顯示文字標籤 — 不影響 SVG bounding box - 避免在 custom layer 中執行會改變 DOM 的邏輯(如
truncateText()) — 不同渲染結果可能觸發 resize 迴圈 - 裝飾性覆蓋元素加上
pointerEvents="none"— 避免干擾 nivo 內建互動
相關案例
Sankey 圖表曾遇到相同問題:在 custom layer 中呼叫 truncateText() 改變 SVG 內容,觸發相同的無限迴圈。解法同樣是改用 <foreignObject> + CSS text-overflow: ellipsis。
參見 Drawer Form 資料不更新的解決方案 — 另一個 UI 元件狀態不同步的模式。