first attempt
This commit is contained in:
1 parent
5883a664d0
commit
36df765000
1 file changed
+75
-14
@@ -51,15 +51,64 @@
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Jellyfin's own nav chrome has moved before (left drawer -> top app bar in
|
||||||
|
// 12.x) and will likely move again. Rather than hardcoding selectors that
|
||||||
|
// break on every redesign, find whatever is fixed/sticky and anchored near
|
||||||
|
// the top of the viewport at runtime, and push it down (Y) by the banner's
|
||||||
|
// height so the banner never just sits in front of (Z) live nav chrome.
|
||||||
|
const originalMarginTop = new WeakMap();
|
||||||
|
const offsetElements = new Set();
|
||||||
|
|
||||||
|
function isTopBarCandidate(el) {
|
||||||
|
if (el.id === BANNER_ID || el.closest('#' + BANNER_ID)) return false;
|
||||||
|
const style = getComputedStyle(el);
|
||||||
|
if (style.position !== 'fixed' && style.position !== 'sticky') return false;
|
||||||
|
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) return false;
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
if (rect.width < window.innerWidth * 0.5) return false; // must span most of the viewport width
|
||||||
|
if (rect.height <= 0 || rect.height > window.innerHeight * 0.6) return false; // skip full-page overlays/modals
|
||||||
|
if (rect.top > 400) return false; // must be anchored near the top of the page
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTopBars() {
|
||||||
|
return Array.prototype.filter.call(document.body.querySelectorAll('*'), isTopBarCandidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushDownTopBars(offsetPx) {
|
||||||
|
const candidates = new Set(findTopBars());
|
||||||
|
|
||||||
|
// Restore anything we previously offset that's no longer a candidate
|
||||||
|
// (e.g. replaced by the SPA router on navigation).
|
||||||
|
offsetElements.forEach(function (el) {
|
||||||
|
if (!candidates.has(el) || !document.contains(el)) {
|
||||||
|
if (originalMarginTop.has(el)) {
|
||||||
|
el.style.marginTop = originalMarginTop.get(el);
|
||||||
|
}
|
||||||
|
offsetElements.delete(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
candidates.forEach(function (el) {
|
||||||
|
if (!originalMarginTop.has(el)) {
|
||||||
|
originalMarginTop.set(el, el.style.marginTop || '');
|
||||||
|
}
|
||||||
|
const base = originalMarginTop.get(el);
|
||||||
|
el.style.marginTop = offsetPx > 0
|
||||||
|
? 'calc(' + (base || '0px') + ' + ' + offsetPx + 'px)'
|
||||||
|
: base;
|
||||||
|
offsetElements.add(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (offsetPx > 0 && candidates.size === 0) {
|
||||||
|
console.warn('[custom-banner] No fixed top navigation elements detected to push down; banner may overlap the menu.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyOffset() {
|
function applyOffset() {
|
||||||
const banner = document.getElementById(BANNER_ID);
|
const banner = document.getElementById(BANNER_ID);
|
||||||
const offset = banner ? (banner.offsetHeight + 'px') : '';
|
const offset = banner ? banner.offsetHeight : 0;
|
||||||
const header = document.querySelector('.headerTop');
|
pushDownTopBars(offset);
|
||||||
const drawer = document.querySelector('.mainDrawer');
|
|
||||||
const sections = document.querySelector('.homeSectionsContainer');
|
|
||||||
if (header) header.style.marginTop = offset;
|
|
||||||
if (drawer) drawer.style.marginTop = offset;
|
|
||||||
if (sections) sections.style.marginTop = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHomePage() {
|
function isHomePage() {
|
||||||
@@ -101,15 +150,27 @@
|
|||||||
style.textContent = BANNER_CSS;
|
style.textContent = BANNER_CSS;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|
||||||
// React to SPA navigation
|
// Debounce: the SPA's own DOM churn (list virtualization, image loads, etc.)
|
||||||
window.addEventListener('hashchange', update);
|
// can fire many mutations per second. We don't need per-frame precision for
|
||||||
|
// nav-bar layout, so coalesce bursts instead of rescanning the whole DOM
|
||||||
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after DOMContentLoaded)
|
// on every single mutation.
|
||||||
// subtree: true catches .headerTop wherever it appears in the DOM
|
let debounceTimer = null;
|
||||||
const observer = new MutationObserver(function () {
|
function scheduleUpdate(delay) {
|
||||||
|
clearTimeout(debounceTimer);
|
||||||
|
debounceTimer = setTimeout(function () {
|
||||||
update();
|
update();
|
||||||
applyOffset();
|
applyOffset();
|
||||||
});
|
}, delay || 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
// React to SPA navigation
|
||||||
|
window.addEventListener('hashchange', update);
|
||||||
|
window.addEventListener('resize', function () { scheduleUpdate(); });
|
||||||
|
|
||||||
|
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after
|
||||||
|
// DOMContentLoaded) and re-run on DOM churn, since the nav bar can be
|
||||||
|
// unmounted/remounted by the SPA router.
|
||||||
|
const observer = new MutationObserver(function () { scheduleUpdate(); });
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
update();
|
update();
|
||||||
|
|||||||
Reference in new issue
Block a user