# 渲染與同步

本頁說明 `MDViewer` 何時重新渲染、渲染時做了哪些事，以及與編輯器的捲動同步如何運作。

## 觸發時機

| 來源 | 條件 |
|---|---|
| 編輯器 `keyup` | `autosave` 為真，且按鍵不是方向鍵、`Shift`、`Control`、`Alt`、`CapsLock`、`Escape`、`F1`～`F12`、`Meta` |
| 編輯器格式化方法（`bold()`、`heading()` 等） | `autosave` 為真 |
| `undo()`、`redo()` | `autosave` 為真 |
| `clear()`、`openfile()` 讀取完成 | 一律觸發 |
| `save()` | `autosave` 為假時觸發（`Cmd/Ctrl+S` 或工具列「儲存並預覽」） |
| 直接呼叫 `viewer.init(text)` | 一律觸發 |

`autosave: false` 時，輸入不會即時反映到預覽，只有 `save()` 才會更新。

## 渲染步驟

`viewer.init(txt)` 的流程：

1. 設 `data-saved="1"`，清除上一個計時器
2. 等待 `sync.delay` 毫秒（預設 300；傳 `0` 會因 falsy 回到 300）
3. 決定內容：`txt` → 綁定編輯器的文字 → `emptyContent`，取第一個非空值
4. 呼叫 `transToHTML()`（擴充模式，帶 hashtag 設定）
5. 以 `replaceChildren()` 整段替換預覽器內容
6. 為每個程式碼區塊的複製按鈕綁定 `navigator.clipboard.writeText()`；成功時加 `success` class 1 秒，失敗加 `error` class
7. 每 50 ms 檢查 code-prettify 的全域 `PR` 是否載入；載入後還原捲動位置、執行 `PR.prettyPrint()`，再對整份文件的 `.mermaid` 元素執行 `mermaid.run()`

連續輸入時計時器會被重設，只有停止輸入超過 `sync.delay` 才會真正渲染（debounce）。

## 讀取編輯器文字

預覽器不透過事件拿資料，而是在渲染時讀取編輯器容器中每個 `<div>` 列的 `innerText`：空白列變成單一 `\n`，不換行空白換成一般空白，最後整體 `trim()`。

## 捲動同步

`sync.scroll` 為真時，綁定編輯器的當下會設定編輯器的 `onwheel`：每次滾輪事件把 `deltaY` 加到預覽器的 `scrollTop`。

| 限制 | 說明 |
|---|---|
| 只同步滾輪 | 拖曳捲軸、觸控滑動、鍵盤捲動不會同步 |
| 單向 | 只有編輯器帶動預覽器 |
| 依增量 | 不是比例對應，兩邊內容高度差異大時位置會漂移 |
| 覆寫 `onwheel` | 會取代編輯器容器上既有的 `onwheel` handler |

## 儲存狀態旗標

編輯器與預覽器容器的 `data-saved` 屬性反映內容是否已同步：輸入後設為 `0`（`unsave()`），渲染開始時設為 `1`。`NanoMD.css` 依此屬性顯示狀態，也可用於自訂「未儲存」提示。

## 虛擬 DOM

`vDOM` 類別與 `#applyPatch()` 仍在原始碼中，但目前渲染改用 `replaceChildren()` 整段替換以確保正確性，diff 與 patch 都沒有被呼叫。
