fix(leaderboard): fly-down landed at (0,0) when polling re-rendered list
Wenn das 5s-Polling während der Fly-Animation feuerte, hat
fillLeaderboardContainer den destEl-Eintrag aus dem DOM entfernt.
getBoundingClientRect() lieferte dann {0,0,0,0} und der Ghost flog
sichtbar in die linke obere Ecke.
Fix:
- flyAnimationActive-Flag suspendiert updateLeaderboardDisplay
während der Animation; ausstehendes Update wird im cleanup
nachgeholt
- Defensive isConnected/Rect-Checks vor jedem Schritt;
bei Orphan wird sauber abgebrochen statt nach (0,0) zu fliegen
- Cleanup in eine Funktion ausgelagert für mehrere Exit-Pfade
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -465,11 +465,22 @@
|
||||
}
|
||||
|
||||
// Leaderboard Funktionen
|
||||
// Während der Fly-Down-Animation darf das Leaderboard NICHT neu
|
||||
// gerendert werden — sonst wird destEl aus dem DOM entfernt und
|
||||
// getBoundingClientRect() liefert {0,0,0,0}; der Ghost fliegt dann
|
||||
// sichtbar in die linke obere Ecke.
|
||||
let flyAnimationActive = false;
|
||||
let leaderboardUpdatePending = false;
|
||||
|
||||
async function loadLeaderboard() {
|
||||
try {
|
||||
const response = await fetch("/api/leaderboard");
|
||||
leaderboardData = await response.json();
|
||||
updateLeaderboardDisplay();
|
||||
if (flyAnimationActive) {
|
||||
leaderboardUpdatePending = true;
|
||||
} else {
|
||||
updateLeaderboardDisplay();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Leaderboards:", error);
|
||||
}
|
||||
@@ -552,9 +563,14 @@
|
||||
|
||||
function flyDownFromSnapshot(srcSnap, destEl) {
|
||||
if (!srcSnap || !destEl) return;
|
||||
if (!destEl.isConnected) return;
|
||||
const dstRect = destEl.getBoundingClientRect();
|
||||
if (dstRect.width === 0 || dstRect.height === 0) return;
|
||||
|
||||
// Polling-getriebenes Re-Rendering des Leaderboards während der
|
||||
// Animation unterdrücken (sonst wird destEl orphaned).
|
||||
flyAnimationActive = true;
|
||||
|
||||
// ---- Phase 1: bestehende Einträge nach unten "schieben" ----
|
||||
// Wir verstecken den (bereits gerenderten) neuen Top-Eintrag und
|
||||
// setzen die Geschwister visuell an die Position, die sie VOR
|
||||
@@ -595,7 +611,39 @@
|
||||
|
||||
// ---- Phase 2: nach dem Slide den Ghost einfliegen lassen ----
|
||||
const flyMs = 800;
|
||||
|
||||
// Sauberes Aufräumen — wird in jedem Exit-Pfad aufgerufen
|
||||
const cleanup = (ghost) => {
|
||||
if (ghost) ghost.remove();
|
||||
if (destEl.isConnected) destEl.style.visibility = "";
|
||||
siblings.forEach((s) => {
|
||||
if (!s.isConnected) return;
|
||||
s.style.transition = "";
|
||||
s.style.transform = "";
|
||||
});
|
||||
flyAnimationActive = false;
|
||||
// Während Animation aufgelaufenes Update jetzt nachholen
|
||||
if (leaderboardUpdatePending) {
|
||||
leaderboardUpdatePending = false;
|
||||
updateLeaderboardDisplay();
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
// Wenn destEl in der Zwischenzeit aus dem DOM verschwand
|
||||
// (z. B. Modus-Wechsel), Animation abbrechen statt nach (0,0)
|
||||
// zu fliegen.
|
||||
if (!destEl.isConnected) {
|
||||
cleanup(null);
|
||||
return;
|
||||
}
|
||||
const destTimeSpanCheck = destEl.querySelector(".time") || destEl;
|
||||
const preRect = destTimeSpanCheck.getBoundingClientRect();
|
||||
if (preRect.width === 0 || preRect.height === 0) {
|
||||
cleanup(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const ghost = document.createElement("div");
|
||||
ghost.textContent = srcSnap.text;
|
||||
ghost.style.position = "fixed";
|
||||
@@ -630,7 +678,7 @@
|
||||
// Reflow erzwingen, damit die Anfangsposition wirkt
|
||||
ghost.getBoundingClientRect();
|
||||
|
||||
// Endwerte: aktuelle Position des .time-Spans im Eintrag
|
||||
// Endwerte aus erneuter Messung (preRect war Vorab-Check)
|
||||
const destTimeSpan = destEl.querySelector(".time") || destEl;
|
||||
const destTimeRect = destTimeSpan.getBoundingClientRect();
|
||||
const destStyle = window.getComputedStyle(destTimeSpan);
|
||||
@@ -643,15 +691,7 @@
|
||||
ghost.style.color = destStyle.color;
|
||||
ghost.style.textShadow = "0 0 6px rgba(0, 255, 136, 0.55)";
|
||||
|
||||
setTimeout(() => {
|
||||
ghost.remove();
|
||||
destEl.style.visibility = "";
|
||||
// Sibling-Transforms aufräumen (sie sind eh schon bei 0)
|
||||
siblings.forEach((s) => {
|
||||
s.style.transition = "";
|
||||
s.style.transform = "";
|
||||
});
|
||||
}, flyMs + 20);
|
||||
setTimeout(() => cleanup(ghost), flyMs + 20);
|
||||
}, slideMs);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user