起きていたこと
タイムラインの上部メニューで「game」を選ぶと、投稿が次の順に並んでいた。
2026-09-27
2026-09-27
2026-07-19
2026-07-02
2026-09-26 ← ここで急に新しい日付に戻る
2026-09-25
新しい順に並ぶはずなのに、途中で 9 月に戻ってしまう。
原因
サーバーは毎回きちんと新しい順に並べて返していた。問題はブラウザ側にあった。
- 先に「movie」などで絞り込んでスクロールすると、7 月の動画付き投稿まで読み込まれる。この投稿にはゲーム情報も付いていた。
- 「game」に切り替えると、すでに読み込み済みの投稿からゲーム付きのものだけが表示される(9/27・7/19・7/2)。
- 同時に、取りこぼしを防ぐために「続きをどこから読むか」の目印が、絞り込みなしの位置(9/27 付近)まで巻き戻される。
- スクロールすると 9/26・9/25 が届くが、新しいカードは常に一番下に追加していたため、7/2 の後ろに並んでしまった。
「巻き戻して新しめの投稿を取り直す」のに「置き場所はいつも末尾」という、2 つの仕組みの食い違いが原因だった。
直し方
並び替え用の数値をカードに持たせる
各カードにはもともと日時の文字列が付いていたが、「2026/3/8」と「2026-03-08」のように書式が混ざっていて、そのままでは比較に使えない。そこでサーバーが並び替えのために計算している数値(タイムスタンプ)を、カードの属性として一緒に出力するようにした。
初回表示と無限スクロールの追加分は別のファイルで描画しているので、両方に同じ属性を足している。
正しい位置に差し込む
新しいカードを追加するとき、画面上のカードを上から見ていき、「自分より古い最初のカード」の直前に差し込む。見つからなければ従来どおり一番下に追加する。
const insertInOrder = (node: HTMLElement) => {
const ts = Number(node.getAttribute('data-sort-ts')) || 0;
if (!ts) { container.appendChild(node); return; }
for (const el of container.querySelectorAll(':scope > .timeline-item[data-sort-ts]')) {
const t = Number(el.getAttribute('data-sort-ts')) || 0;
if (t && t < ts) { container.insertBefore(node, el); return; }
}
container.appendChild(node);
};
時刻が同じ場合は既存カードの後ろに入るので、これまでの並びは変わらない。
変えなかったこと
- 取りこぼし防止のための「巻き戻し」はそのまま残した。これを消すと、今度は投稿が抜け落ちる別の不具合が戻ってしまう。
- サーバー側の処理とデータベースへの問い合わせは一切変えていない。属性を 1 つ出力するだけなので、読み取り量も増えない。