私が試したJSONビューワーはすべて同じことをします:それらはきれいに整形します。インデントを追加し、キーを色付けし、括弧を畳みます。10行の設定には便利ですが、実際に開発者が毎日扱うデータの形状には役立ちません。それはオブジェクトの配列です
。きれいに整形された50行のAPIレスポンスは、まだ500行スクロールしてトップからボトムまで見る必要があります。「管理者のみを表示」라고 말할 수 없습니다。 すべての記録を再読み込みせずに。日付で並べ替えることはできません。行全体で検索することもできません。
それはビューアーの問題ではありません — それは間違った形式です。オブジェクトの配列は テーブル になりたいのです:
┌────┬───────┬───────┬────────┐
│ ID │ NAME │ ROLE │ ACTIVE │
├────┼───────┼───────┼────────┤
│ 1 │ Alice │ admin │ ● Yes │
│ 2 │ Bob │ user │ ● Yes │
│ 3 │ Carol │ user │ ● No │
│ 4 │ Dave │ admin │ ● Yes │
└────┴───────┴───────┴────────┘
並べ替え可能な列。検索可能な行。任意の行をクリックして完全なネストされた詳細を展開します。同じデータ — しかしあなたスキャンそれを使う代わりに読むそれ。
だから私は作りましたprettyjsonxml.com— JSONとXMLビューワーが配列を実際のテーブルに変換(折りたたみ可能なツリービュー、フォーマット、ミニファイ、Base64画像プレビューを含む)。1つのHTMLファイル。バックエンドなし。ビルドステップなし。ブラウザ内で完全に実行;データはあなたのマシンから決して離れません。
思いがけなかったこと:9 MBのAPIレスポンスでテーブルビューが実際に高速になるようにするのは、見た目よりずっと難しい問題だった。ここに編集されていないストーリーがある。
簡単なバージョン:動作する、動かない
V1はシンプルだった:
const data = JSON.parse(text);
renderTable(data);
function renderTable(items) {
const tbody = document.querySelector('tbody');
items.forEach(row => {
const tr = document.createElement('tr');
// ... build cells
tbody.appendChild(tr);
});
}
テストした5行の例には美しかった。その後、実際のAPIレスポンスを貼り付けた。ブラウザが1.5秒間フリーズしました
フリーズの原因は2つありました
-
JSON.parse9 MBのブロックでメインスレッドを約500 ms停止しました - 30,000 × 2行(メイン+詳細)= 60,000のDOMノード作成で別の約1500 msブロックしました
どちらも速くすることはできませんが、UIがフリーズしないようにすることができますです.
フェーズ1:content-visibility: auto — 一行で勝つ
モダンブラウザは、オフスクリーンコンテンツのレイアウトをスキップします。CSSルール:
.data-table tr.row-main {
content-visibility: auto;
contain-intrinsic-size: auto 40px;
}
content-visibility: autoはブラウザに伝えます:「この要素のレイアウトを計算しないで、スクロールして画面に表示される直まで待って。」 contain-intrinsic-size はプレースホルダーの高さを与え、スクロールバーがまだ完全なドキュメントを表すようにします。
レンダリング時間は変わらず、作業はまだ行われますが、感じられるパフォーマンスが向上しました。ブラウザが最初に表示される部分を描画できるからです。犯罪のように活用されていません。現代のブラウザの約95%で動作します。
フェーズ2:JSON.parseのWeb Workers — 反直感の教訓
次のフリーズはJSON.parseそのものだった。常識的なアプローチ:高価なパース処理をメインスレッドからWeb Workerで切り離す。
const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = (e) => {
const parsed = JSON.parse(e.data);
self.postMessage(parsed);
};
`], { type: 'application/javascript' })));
worker.postMessage(largeJsonString);
worker.onmessage = (e) => render(e.data);
完了。メインスレッドは反応的だ。正しい?
実際にはもっと遅かった。
その理由は:ワーカーがパースされたオブジェクトを送り返す際にpostMessage、メインスレッドは構造的クローンオブジェクトグラフ全体を取得するために、メインスレッド上でも。30,000オブジェクトの配列の場合、そのクローンは300~500ミリ秒かかり、
。それで私は成功して500ミリ秒のJSON.parseをメインスレッドから移し、400ミリ秒の構造的クローンを追加した。合計で100ミリ秒の改善。しかしユーザーはフリーズを感じる。は後、すぐに結果が期待されるボタンをクリックした後に流れでが来ます。
Web Workersは、結果が戻ってくる必要がないCPUバウンドの作業に使います。 95%の作業負荷がパースされたオブジェクトそのものである場合、構造クローンのコストが利益を支配します。
フォーマット/ミニファイ用の作業員を保持しました(出力は文字列で、コピーが安価です)。パース後にレンダリングするフローでは、ほとんど利益がなかったです。本当の修正は他にありました
フェーズ3:バーチャルスクロール — 本当の修正
3万行のテーブルの場合、30,000行をレンダリングしません。ユーザーが見ることができる~50行をレンダリングし、スクロールするときにそれらを交換します
<table>の罠は:あなたはできませんposition: absolute 行(テーブルレイアウトでは許可されていません)。代わりに、スパーサーレイアウトを使用してください:
<table>
<thead>...</thead>
<tbody>
<tr style="height:850px"></tr> <!-- spacer for rows above viewport -->
<tr>row 21</tr>
<tr>row 22</tr>
...
<tr>row 70</tr>
<tr style="height:1200px"></tr> <!-- spacer for rows below viewport -->
</tbody>
</table>
スクロールイベントごとに、表示される行を再計算し、それらを交換します:
function onScroll() {
const tbodyRect = tbody.getBoundingClientRect();
const viewTop = Math.max(0, -tbodyRect.top);
const viewBottom = viewTop + scrollContainer.clientHeight;
const startRow = Math.max(0, Math.floor(viewTop / ROW_HEIGHT) - 10);
const endRow = Math.min(items.length, Math.ceil(viewBottom / ROW_HEIGHT) + 10);
// Remove old visible rows, build new ones from items[startRow..endRow]
// Adjust spacer heights so scrollbar position stays correct
}
3万行のJSON配列では、「タブが完全に反応しない」から「スムーズな60fpsスクロール」に変わります。より控えめなサイズでも——例えば数百行——の利点は、検索とソートがインスタントになることです。なぜなら、それらは今やJavaScript配列上で動作するからです。何千ものDOMノードをたどるのではなく。
20分間失ったバグは:私のバーチャルスクローラーがリスニングしていたwindow.scroll ですが、私のページには body { overflow: hidden } と <main> { overflow: auto } がありましたので、window.scroll は決して されませんでした。実際のスクロールイベントは <main> から来ていました。
// Walk up to find the nearest ancestor that actually scrolls
function findScrollContainer(el) {
let p = el.parentElement;
while (p && p !== document.body) {
const oy = getComputedStyle(p).overflowY;
if ((oy === 'auto' || oy === 'scroll') && p.scrollHeight > p.clientHeight) return p;
p = p.parentElement;
}
return window;
}
常に実行時にスクロールコンテナを解決してください。window であると仮定しないでください。
テキストエリアの罠
予期せぬ凍結がもう一つ:9 MBの文字列を<textarea>.valueに割り当てると、それだけでメインスレッドが~300–500 msブロックされる。ブラウザは折りたたまれた部分のほとんどが下にあるにもかかわらず、テキストコンテンツのレイアウトを計算しなければならない。
修正案:ファイル> 5 MBを超える場合、テキストエリアを空にして、スタイル付きの「ロード状態」パネルを表示する代わりに。
if (file.size > 5 * 1024 * 1024) {
editor.value = '';
showLoadedOverlay(file.name, file.size);
} else {
editor.value = text;
}
データはまだビューアーにパースされ表示されますが、編集可能なテキストエリアには表示されません。9MBのファイルに対しては、誰も手動で編集したくないのでしょう
もう少し違う方法でやるべきこと
- バーチャルスクロールから始めるそれをフェーズ3として追加しない。それが唯一スケールするものです。他のすべてはポリッシュです
- 「Workerに移動する」という反射を疑う ワーカーは不要なバックアップを計算するのに最適です。パース後にクローンするフローには向きません.
-
適用可能な場所に
content-visibility: autoを使用してください.基本的に無料です. - 本番データで早期にテストしてください.私の5行のテストケースはすべての興味深いバグを隠しました.
単一ファイルのこと
自分自身と議論し続けていたのはビルドステップを追加するかどうかの件。「ただバンドルして、ただモジュールに分けて、ただTypeScriptを追加して…」そのあらゆるプロトタイプは技術的にクリーンで、物質的に悪かった——今はもっとファイルをホストしなければならず、キャッシュバスティングの問題を心配し、ツールチェーンをメンテナンスしなければならなかった.
「100%ブラウザ内で、サーバー不要」というツールの全体の訴求を持ち、それを一つのHTMLファイルとして配信するにはSave Asをオフラインで実行するのが正しい製品の決定です。プラグマを純粋さの上に置きます.
最終版:~225 KBの単一HTML、依存関係なし、ビルド不要、Cloudflare Pagesからそのまま提供.
試してみたい場合は:prettyjsonxml.com — 任意のJSONまたはXMLを貼り付け、ソート可能な表または折りたたみ可能な木として表示します。必要だったから作りました。おそらくあなたも同じように必要かもしれないから共有します。
ビッグデータUIのためのパフォーマンスのテクニックは何を使いましたか?人々を驚かせたものについていつも興味があります。












