dynamic-toc

Von $1

    Inhaltsverzeichnis
    keine Gliederung

    Version seit 15:43, 5 Nov 2026

    zu dieser Version.

    Zurück zu Versionshistorie.

    Zeige aktuelle Version

    (function () {
        'use strict';
    
        function startTracker() {
            var CONFIG = {
                selectors : 'h2, h3, h4',
                threshold : 0.35,
                throttle  : 80,
                range     : 2,
                excludeTexts : ['Von $1', '*Edit'],
                excludeContainers : ['.deki-page-titlex', '#siteSubx'],
                bottomDefault : 110,
                bottomMenuOpen : 300,
                maxRetries : 30,
                retryDelay : 300
            };
    
            var panel = document.getElementById('trackerPageContent');
            var list  = document.getElementById('trackerList');
            var menuToggle = document.querySelector('.menu-toggle-icon');
    
            if (!panel || !list) return;
    
            var headings = [];
            var currentIndex = -1;
            var timer;
            var menuOpen = false;
    
            if (menuToggle) {
                menuToggle.addEventListener('click', function () {
                    menuOpen = !menuOpen;
                    panel.style.bottom = menuOpen ? CONFIG.bottomMenuOpen + 'px' : CONFIG.bottomDefault + 'px';
                });
            }
    
            function collectHeadings() {
                var seenTexts = [];
                headings = Array.from(document.querySelectorAll(CONFIG.selectors))
                    .filter(function (h) {
                        for (var i = 0; i < CONFIG.excludeContainers.length; i++) {
                            if (h.closest(CONFIG.excludeContainers[i])) return false;
                        }
                        return true;
                    })
                    .map(function (h) {
                        return { el: h, text: h.textContent.trim() };
                    })
                    .filter(function (h) {
                        for (var i = 0; i < CONFIG.excludeTexts.length; i++) {
                            var pattern = CONFIG.excludeTexts[i];
                            if (pattern.startsWith('*') && h.text.endsWith(pattern.substring(1))) return false;
                            if (pattern.endsWith('*') && h.text.startsWith(pattern.slice(0, -1))) return false;
                            if (h.text === pattern) return false;
                        }
                        if (seenTexts.includes(h.text)) return false;
                        seenTexts.push(h.text);
                        return true;
                    });
            }
    
            function getTop(el) {
                var rect = el.getBoundingClientRect();
                return rect.top + (window.pageYOffset || document.documentElement.scrollTop);
            }
    
            function findCurrent() {
                if (!headings.length) return 0;
                var scrollPos = window.pageYOffset || document.documentElement.scrollTop;
                var triggerPoint = scrollPos + (window.innerHeight * CONFIG.threshold);
                
                for (var i = headings.length - 1; i >= 0; i--) {
                    var top = getTop(headings[i].el);
                    if (triggerPoint >= top) {
                        return i;
                    }
                }
                return 0;
            }
    
            function render() {
                list.innerHTML = '';
                if (!headings.length) return;
                var start = Math.max(0, currentIndex - CONFIG.range);
                var end = Math.min(headings.length - 1, currentIndex + CONFIG.range);
                
                for (var i = start; i <= end; i++) {
                    var item = document.createElement('div');
                    item.className = 'tracker-item';
                    if (i === currentIndex) item.classList.add('current');
                    else if (Math.abs(i - currentIndex) === 1) item.classList.add('near');
                    else item.classList.add('far');
                    item.textContent = headings[i].text;
                    list.appendChild(item);
                }
            }
    
            function update() {
                if (!headings.length) return;
                
                var idx = findCurrent();
                
                if (idx !== currentIndex) {
                    currentIndex = idx;
                    render();
                }
            }
    
            window.addEventListener('scroll', function () {
                clearTimeout(timer);
                timer = setTimeout(update, CONFIG.throttle);
            }, { passive: true });
    
            window.addEventListener('resize', update);
    
            function init(attempt) {
                collectHeadings();
                
                if (headings.length > 0) {
                    currentIndex = findCurrent();
                    render();
                    return;
                }
                
                if (attempt < CONFIG.maxRetries) {
                    setTimeout(function() {
                        init(attempt + 1);
                    }, CONFIG.retryDelay);
                }
            }
    
            setTimeout(function() {
                init(0);
            }, 1000);
        }
    
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', startTracker);
        } else {
            startTracker();
        }
    })();
    
    <div id="trackerPageContent">
     <div class="tracker-list" id="trackerList"></div>
    </div>
    :root { 
        --tracker-width: 250px;
        --tracker-bottom: 110px;
        --tracker-left: 20px;
        --size-current: 22px;
        --size-near: 16px;
        --size-far: 14px;
        --color-current: #0066CC;
        --color-near: #777777;
        --color-far: #7777776b;
    }
    
    #trackerPageContent {
        position: fixed;
        bottom: 110px;
        left: var(--tracker-left);
        width: var(--tracker-width);
        z-index: 2147483000;
        pointer-events: none;
        opacity: 1;
        transition: bottom 0.3s ease, opacity 0.4s ease;
    }
    
    .tracker-list {
        display: flex;
        flex-direction: column;
        gap: 6px;
    }
    
    .tracker-item {
        line-height: 1.35;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        transition: all 0.25s ease;
    }
    
    .tracker-item.current {
        font-size: var(--size-current);
        font-weight: 600;
        color: var(--color-current);
    }
    
    .tracker-item.near {
        font-size: var(--size-near);
        color: var(--color-near);
    }
    
    .tracker-item.far {
        font-size: var(--size-far);
        color: var(--color-far);
    }
    

     
    Use the language selection below for English Utilise le sélecteur de langue ci-dessous pour français Per l'italiano, utilizzare la selezione della lingua qui sotto Schön sprichst du deutsch :-)