From 84fdfe85ac87769e0a9c05e0460a71f4fbf6a8a6 Mon Sep 17 00:00:00 2001 From: Dome Date: Wed, 22 Apr 2026 01:25:10 +0200 Subject: [PATCH] TOC Refinment feat(toc): improve footer collision handling and initial render - implement transform-based footer collision (no layout shift) - add dynamic trigger offset for smoother early interaction - introduce soft easing for natural TOC push-back - fix initial TOC visibility with requestAnimationFrame - ensure stable positioning without top/position overrides Result: smooth, flicker-free TOC behavior with proper footer boundary handling --- js/toc.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++-------- style.css | 35 +++++++++++++++++++------------ 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/js/toc.js b/js/toc.js index c732aa7..1d1de02 100644 --- a/js/toc.js +++ b/js/toc.js @@ -2,7 +2,7 @@ document.addEventListener('DOMContentLoaded', function () { var toc = document.getElementById('zeitfresser-floating-toc'); var title = document.querySelector('.zeitfresser-article-heading .page-title, .zeitfresser-article-heading .entry-title, .entry-header .entry-title'); var progressBar = document.getElementById('zeitfresser-floating-toc-progress'); - var nav = toc.querySelector('.zeitfresser-floating-toc__nav'); + var nav = toc ? toc.querySelector('.zeitfresser-floating-toc__nav') : null; if (!toc || !title) { return; @@ -13,7 +13,6 @@ document.addEventListener('DOMContentLoaded', function () { var stickyTop = 100; var headingOffset = 88; var ticking = false; - var isInitialized = false; // 👈 NEW function isDesktop() { return desktopQuery.matches; @@ -46,8 +45,19 @@ document.addEventListener('DOMContentLoaded', function () { var titleRect = title.getBoundingClientRect(); var scrollTop = window.scrollY || window.pageYOffset || 0; - var contentColumn = document.querySelector('.inside-page .main-wrapper > *:first-child, .inside-page .main-wrapper .primary-content, .inside-page .main-wrapper #primary, .inside-page .main-wrapper main'); - var sidebar = document.querySelector('.inside-page .main-wrapper > aside, .inside-page .main-wrapper .widget-area, .inside-page .main-wrapper #secondary, .inside-page .main-wrapper .sidebar'); + var contentColumn = document.querySelector( + '.inside-page .main-wrapper > *:first-child, ' + + '.inside-page .main-wrapper .primary-content, ' + + '.inside-page .main-wrapper #primary, ' + + '.inside-page .main-wrapper main' + ); + + var sidebar = document.querySelector( + '.inside-page .main-wrapper > aside, ' + + '.inside-page .main-wrapper .widget-area, ' + + '.inside-page .main-wrapper #secondary, ' + + '.inside-page .main-wrapper .sidebar' + ); var contentRect = contentColumn ? contentColumn.getBoundingClientRect() : titleRect; var sidebarRect = sidebar ? sidebar.getBoundingClientRect() : null; @@ -66,11 +76,35 @@ document.addEventListener('DOMContentLoaded', function () { document.documentElement.style.setProperty('--zeitfresser-toc-top', tocTop + 'px'); document.documentElement.style.setProperty('--zeitfresser-toc-left', tocLeft + 'px'); document.documentElement.style.setProperty('--zeitfresser-toc-width', tocWidth + 'px'); + } - // 👇 NEW: reveal after first correct positioning - if (!isInitialized) { - toc.classList.add('is-ready'); - isInitialized = true; + /** + * ✅ FINAL: Footer Collision ohne Layout-Bruch + * Nutzt nur transform statt top/position + */ + function handleFooterCollision() { + if (!isDesktop()) { + toc.style.transform = ''; + return; + } + + var footer = document.querySelector('footer.site-footer, footer, #colophon'); + if (!footer) return; + + var footerRect = footer.getBoundingClientRect(); + var tocRect = toc.getBoundingClientRect(); + + var offset = 40; // finaler Abstand + var triggerOffset = Math.min(200, window.innerHeight * 0.2); + + var overlap = tocRect.bottom - (footerRect.top - triggerOffset); + + if (overlap > -offset) { + var strength = 0.85; + var correction = Math.max(0, (overlap + offset) * strength); + toc.style.transform = 'translateY(-' + correction + 'px)'; + } else { + toc.style.transform = ''; } } @@ -90,7 +124,10 @@ document.addEventListener('DOMContentLoaded', function () { function updateProgress() { if (!progressBar) return; - var article = document.querySelector('.single-post .post-content article, .single-post .post-content, article.post, article'); + var article = document.querySelector( + '.single-post .post-content article, ' + + '.single-post .post-content, article.post, article' + ); if (!article) { progressBar.style.width = '0%'; @@ -106,6 +143,7 @@ document.addEventListener('DOMContentLoaded', function () { function updateActiveHeading() { var headings = getHeadings(); + if (!headings.length) return; var currentId = headings[0].target.id; @@ -127,6 +165,7 @@ document.addEventListener('DOMContentLoaded', function () { window.requestAnimationFrame(function () { syncPosition(); + handleFooterCollision(); // 👈 stabil integriert updateProgress(); updateActiveHeading(); ticking = false; @@ -168,9 +207,14 @@ document.addEventListener('DOMContentLoaded', function () { // Initial run syncPosition(); + handleFooterCollision(); updateProgress(); updateActiveHeading(); + requestAnimationFrame(function () { + toc.classList.add('is-visible'); + }); + window.addEventListener('scroll', onViewportChange, { passive: true }); window.addEventListener('resize', onViewportChange, { passive: true }); }); diff --git a/style.css b/style.css index 370ae9c..b646b14 100644 --- a/style.css +++ b/style.css @@ -42,25 +42,33 @@ html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:b left:var(--zeitfresser-toc-left, 24px) !important; width:var(--zeitfresser-toc-width) !important; - opacity:0 !important; - transition:opacity .2s ease !important; - max-height:calc(100vh - var(--zeitfresser-toc-top, 100px) - var(--zeitfresser-toc-top-offset, 0px) - 36px) !important; + padding:0 !important; border:none !important; border-radius:0 !important; + background:transparent !important; background-color:transparent !important; backdrop-filter:none !important; box-shadow:none !important; + overflow:hidden !important; - transform:none !important; + + transform:none; pointer-events:auto !important; + z-index:80; + contain: layout style; + + opacity: 0; + transition: opacity 0.12s ease-out; + + will-change: opacity, transform; } -.zeitfresser-floating-toc.is-ready{ - opacity:1 !important; +.zeitfresser-floating-toc.is-visible{ + opacity: 1; } .zeitfresser-floating-toc__header{ @@ -130,17 +138,19 @@ html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:b margin:0 !important; padding:0 0 0 14px !important; border-left:3px solid transparent !important; - border-radius:0 !important; + background:none !important; - background-color:transparent !important; - background-image:none !important; box-shadow:none !important; + color:#f7f7fa !important; font-size:.92rem !important; line-height:1.35 !important; font-weight:400 !important; + text-decoration:none !important; + transition:color .18s ease, border-color .18s ease !important; + word-break:normal !important; overflow-wrap:normal !important; } @@ -167,13 +177,12 @@ html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:b color:#bd93f9 !important; border-left-color:#bd93f9 !important; background:none !important; - background-color:transparent !important; - background-image:none !important; outline:none !important; box-shadow:none !important; text-decoration:none !important; } +/* Remove default anchor jump highlight */ .single-post .entry-content :target, .post-content :target, .inner-article-content :target, @@ -181,12 +190,12 @@ html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:b .post-content :focus, .inner-article-content :focus{ background:transparent !important; - background-color:transparent !important; outline:none !important; box-shadow:none !important; border:none !important; } +/* Offset for anchor scroll */ .single-post .inner-article-content h2, .single-post .inner-article-content h3, .single-post .inner-article-content h4, @@ -196,9 +205,9 @@ html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:b scroll-margin-top:88px; } +/* Disable TOC on smaller screens */ @media only screen and (max-width:1650px){ .zeitfresser-floating-toc{ display:none !important; } } -