Inhaltsverzeichnis
keine Gliederung// =============================================================================
// HEADING TRACKER - Finale Version mit robuster Höhen-Erkennung
// Zeigt aktuelle Position auf langen Seiten anhand von Überschriften
// Fix: Funktioniert auch bei html/body { height: 100% }
// =============================================================================
!function() {
function startTracker() {
// DOM Elemente
var panel = document.getElementById("trackerPageContent");
var list = document.getElementById("trackerList");
if (!panel || !list) return;
// State
var headings = [];
var currentIndex = -1;
var timer;
var isVisible = 1;
var lastScrollPos = 0;
// =============================================================================
// KONFIGURATION
// =============================================================================
var CONFIG = {
selectors: "h2,h3,h4", // Welche Headings tracken
threshold: 0.35, // Trigger bei 35% Viewport
throttle: 80, // Scroll delay in ms
range: 2, // Anzahl Headings vor/nach aktuellem
minH: 3, // Minimum Headings für Aktivierung
minPage: 1500, // Minimum Seitenhöhe in px
hideTop: 1000, // Verstecke wenn < 1000px von oben
excludeTexts: ["Von $1", "*Edit"], // Text-Filter (Prefix/Suffix)
excludeContainers: [ // Container die ignoriert werden
".deki-page-titlex",
"#siteSubx"
]
};
// =============================================================================
// ROBUSTE SEITENHÖHE - Fix für height: 100% Problem
// =============================================================================
function getRealPageHeight() {
var docEl = document.documentElement;
var body = document.body;
return Math.max(
docEl.scrollHeight || 0,
body.scrollHeight || 0,
docEl.offsetHeight || 0,
body.offsetHeight || 0,
docEl.clientHeight || 0,
body.clientHeight || 0
);
}
// =============================================================================
// PRÜFE OB TRACKER AKTIVIERT WERDEN SOLL
// =============================================================================
function shouldActivate() {
var pageHeight = getRealPageHeight();
if (pageHeight < CONFIG.minPage) return 0;
collectHeadings();
return headings.length >= CONFIG.minH;
}
// =============================================================================
// SAMMLE HEADINGS MIT FILTERN
// =============================================================================
function collectHeadings() {
var allHeadings = document.querySelectorAll(CONFIG.selectors);
var seenTexts = [];
var tempHeadings = [];
for (var i = 0; i < allHeadings.length; i++) {
var element = allHeadings[i];
var skip = 0;
// Prüfe excludeContainers
for (var j = 0; j < CONFIG.excludeContainers.length; j++) {
var container = document.querySelector(CONFIG.excludeContainers[j]);
if (container && container.contains(element)) {
skip = 1;
break;
}
}
if (skip) continue;
// Text extrahieren und trimmen
var text = (element.textContent || element.innerText).replace(/^\s+|\s+$/g, "");
// Prüfe excludeTexts
for (var k = 0; k < CONFIG.excludeTexts.length; k++) {
var pattern = CONFIG.excludeTexts[k];
// Suffix-Match (*Edit)
if (pattern.charAt(0) === "*") {
var suffix = pattern.substring(1);
if (text.indexOf(suffix, text.length - suffix.length) !== -1) {
skip = 1;
break;
}
}
// Prefix-Match mit Wildcard (Von $1)
else if (pattern.indexOf("$1") !== -1) {
var prefix = pattern.split("$1")[0];
if (text.indexOf(prefix) === 0) {
skip = 1;
break;
}
}
// Exact Match
else if (text === pattern) {
skip = 1;
break;
}
}
if (skip) continue;
// Prüfe Duplikate
var isDuplicate = 0;
for (var m = 0; m < seenTexts.length; m++) {
if (seenTexts[m] === text) {
isDuplicate = 1;
break;
}
}
if (!isDuplicate) {
seenTexts.push(text);
tempHeadings.push({
el: element,
text: text
});
}
}
headings = tempHeadings;
}
// =============================================================================
// BERECHNE ABSOLUTE POSITION EINES ELEMENTS
// =============================================================================
function getTop(element) {
var rect = element.getBoundingClientRect();
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return rect.top + scrollTop;
}
// =============================================================================
// FINDE AKTUELLES HEADING
// =============================================================================
function findCurrent() {
if (!headings.length) return 0;
var scrollPos = window.pageYOffset || document.documentElement.scrollTop;
var viewportHeight = window.innerHeight;
var pageHeight = getRealPageHeight();
// Am Ende der Seite?
if (scrollPos + viewportHeight >= pageHeight - 100) {
return headings.length - 1;
}
// Finde Heading bei Trigger-Point
var triggerPoint = scrollPos + viewportHeight * CONFIG.threshold;
for (var i = headings.length - 1; i >= 0; i--) {
if (triggerPoint >= getTop(headings[i].el)) {
return i;
}
}
return 0;
}
// =============================================================================
// RENDERE TRACKER
// =============================================================================
function render() {
list.innerHTML = "";
if (headings.length) {
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.className += " current";
} else if (Math.abs(i - currentIndex) === 1) {
item.className += " near";
} else {
item.className += " far";
}
item.textContent = headings[i].text;
list.appendChild(item);
}
}
}
// =============================================================================
// VISIBILITY CHECK - VERSTECKE BEI < 1000PX VON OBEN
// =============================================================================
function checkVisibility() {
var scrollPos = window.pageYOffset || document.documentElement.scrollTop;
if (scrollPos < CONFIG.hideTop) {
if (isVisible) {
panel.style.opacity = "0";
isVisible = 0;
}
} else {
if (!isVisible) {
panel.style.opacity = "1";
isVisible = 1;
}
}
}
// =============================================================================
// UPDATE BEI SCROLL
// =============================================================================
function update() {
var scrollPos = window.pageYOffset || document.documentElement.scrollTop;
// Throttle: Nur bei signifikanter Bewegung
if (Math.abs(scrollPos - lastScrollPos) < 10) return;
lastScrollPos = scrollPos;
checkVisibility();
var newIndex = findCurrent();
if (newIndex !== currentIndex) {
currentIndex = newIndex;
render();
}
}
// =============================================================================
// INITIALISIERUNG
// =============================================================================
function init() {
if (!shouldActivate()) {
panel.style.display = "none";
return;
}
panel.style.opacity = "1";
currentIndex = findCurrent();
render();
window.addEventListener("scroll", function() {
clearTimeout(timer);
timer = setTimeout(update, CONFIG.throttle);
}, false);
checkVisibility();
}
setTimeout(init, 1500);
}
// =============================================================================
// AUTO-START
// =============================================================================
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", startTracker);
} else {
startTracker();
}
}();
<div id="trackerPageContent">
<div class="tracker-list" id="trackerList"></div>
</div>
/* =============================================================================
TRACKER STYLING
============================================================================= */
#trackerPageContent {
position: fixed;
bottom: 110px;
left: 20px;
width: 250px;
z-index: 2147483000;
pointer-events: none;
opacity: 0;
transition: 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;
}
/* Aktuelles Heading - groß & blau */
.tracker-item.current {
font-size: 22px;
font-weight: 600;
color: #0066CC;
}
/* Nächste Headings - mittel */
.tracker-item.near {
font-size: 16px;
color: #777777;
}
/* Entfernte Headings - klein */
.tracker-item.far {
font-size: 14px;
color: #999999;
}