Lift player to body for iOS fullscreen so the app never shows around it

This commit is contained in:
Jonathan Sykes
2026-10-06 09:14:06 +08:00
parent a143c8331d
commit 63ff2173d8
2 changed files with 13 additions and 2 deletions

View File

@@ -17,6 +17,12 @@
let active = false, custom = false, native = false, locked = false, angle = 0, timer, hideTimer;
const ancestors = [];
for (let el = stage.parentElement; el && el !== document.body; el = el.parentElement) ancestors.push(el);
// iOS (no element fullscreen) draws its own fixed overlay. Any ancestor with a filter,
// backdrop-filter, containment or clipping traps a fixed child, which showed the app
// around the video on iPhone. Lift the stage to <body> for the overlay and put it back after.
let home = null;
function lift() { if (home || stage.parentElement === document.body) return; home = { parent: stage.parentElement, next: stage.nextSibling }; document.body.append(stage); }
function unlift() { if (!home) return; const { parent, next } = home; home = null; if (next && next.parentNode === parent) parent.insertBefore(stage, next); else parent.append(stage); }
const hud = document.createElement('div'); hud.className = 'fullscreen-hud'; hud.hidden = true;
hud.innerHTML = '<div class="fullscreen-top"><div class="fullscreen-title" aria-live="polite"></div><button type="button" aria-label="Exit fullscreen" title="Exit fullscreen"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 4H4v5m0-5 6 6m5 9h5v-5m0 5-6-6"/></svg></button></div><div class="fullscreen-controls"><button type="button" aria-label="Play or pause" title="Play or pause"><svg class="fill" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></button><input type="range" min="0" max="1000" value="0" aria-label="Seek video"><button type="button" class="fullscreen-orientation" aria-label="Change fullscreen orientation" title="Change fullscreen orientation"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 8V4h4M20 16v4h-4M4 4l6 6m4 4 6 6M20 8V4h-4M4 16v4h4M20 4l-6 6m-4 4-6 6"/></svg></button><button type="button" class="fullscreen-fit" aria-label="Change video fit" title="Change video fit"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7V4h3M17 4h3v3M20 17v3h-3M7 20H4v-3M7 9h10v6H7z"/></svg></button></div>';
stage.append(hud);
@@ -56,7 +62,7 @@
const value = normalize(settings().fullscreenOrientation);
if (!custom && value !== 'auto') {
try { await screen.orientation.lock(value); locked = true; if (!active) { screen.orientation.unlock(); locked = false; return; } }
catch { if (!active) return; custom = true; stage.classList.add('orientation-overlay'); }
catch { if (!active) return; custom = true; lift(); stage.classList.add('orientation-overlay'); }
}
layout();
}
@@ -77,6 +83,7 @@
hud.hidden = true; hud.classList.remove('hud-hidden'); stage.classList.remove('orientation-overlay', 'orientation-fullscreen');
delete stage.dataset.fullscreenRotation; document.body.classList.remove('landscape-fs');
delete stage.dataset.fullscreenFit;
unlift();
if (locked) { screen.orientation?.unlock?.(); locked = false; }
}
function leave() {
@@ -92,7 +99,7 @@
if (!stage.requestFullscreen) throw new Error('Custom fullscreen');
await stage.requestFullscreen();
native = true;
} catch { custom = true; stage.classList.add('orientation-overlay'); }
} catch { custom = true; lift(); stage.classList.add('orientation-overlay'); }
if (!active) return;
await orient();
if (!active) return;