PageSourceSearch

https://www.b12-vitamin.com/sticky-sidebar.js?ver=1774334320

js b12-vitamin.com collected 2026-09-27 21:44:22 UTC 9,030 bytes, 270 lines download raw bytes

1/**
2 * Sticky Sidebar (desktop-only) — pure JS fixed-mode (no CSS sticky)
3 * -----------------------------------------------------------------
4 * - Works in legacy float/layouts where CSS sticky fails.
5 * - Desktop-only (>= 1151px).
6 * - Avoids footer overlap and enables internal scrolling when taller than viewport.
7 * - Adds placeholder to prevent layout jumps.
8 * - WordPress-safe, no dependencies.
9 *
10 * Configurable constants are at the top.
11 */
12
13(function () {
14  'use strict';
15
16  // -----------------------------
17  // Config
18  // -----------------------------
19  var DEBUG = false;
20  var DESKTOP_MEDIA = '(min-width: 1151px)';
21  var TOP_OFFSET_BASE = 12;        // base gap to viewport top (px)
22  var BOTTOM_GAP = 12;             // gap above footer (px)
23  var SIDEBAR_SELECTOR = '.sidebar, .Sidebar';
24  var FOOTER_SELECTOR  = 'footer.footer';
25  var INIT_POLL_MS = 120;
26  var INIT_POLL_MAX = 25;
27
28  // -----------------------------
29  // Logging helpers
30  // -----------------------------
31  var NS='[StickySidebar/Fixed]';
32  function log(){ if (DEBUG) console.log.apply(console,[NS].concat([].slice.call(arguments))); }
33  function warn(){ if (DEBUG) console.warn.apply(console,[NS].concat([].slice.call(arguments))); }
34  function info(){ if (DEBUG) console.info.apply(console,[NS].concat([].slice.call(arguments))); }
35  function table(rows){ if (DEBUG && console.table) console.table(rows); }
36
37  // -----------------------------
38  // State
39  // -----------------------------
40  var sidebar, footer, inner, placeholder, mq;
41  var isFixed = false;
42  var startStickY = 0;         // absolute Y (document) ab dem fixiert wird
43  var topOffset = TOP_OFFSET_BASE; // inkl. Adminbar
44  var lastLeft = 0, lastWidth = 0;
45  var ro = null;
46
47  // -----------------------------
48  // Utils
49  // -----------------------------
50  function getScrollY(){ return window.pageYOffset || document.documentElement.scrollTop || 0; }
51  function rect(el){ return el.getBoundingClientRect(); }
52  function absTop(el){ var r=rect(el); return r.top + getScrollY(); }
53  function docHeight(){
54    var b=document.body, e=document.documentElement;
55    return Math.max(b.scrollHeight,e.scrollHeight,b.offsetHeight,e.offsetHeight,b.clientHeight,e.clientHeight);
56  }
57  function footerAbsTop(){ return footer ? absTop(footer) : docHeight(); }
58  function adminBarHeight(){
59    var bar = document.getElementById('wpadminbar');
60    if (!bar) return 0;
61    // typische Werte: 32px Desktop, 46px mobil
62    var h = bar.offsetHeight || 0;
63    return h;
64  }
65
66  // -----------------------------
67  // Core geometry
68  // -----------------------------
69  function computeStart(){
70    // Startpunkt ist der Sidebar-Top (relativ zum Dokument) minus (topOffset)
71    var sRect = rect(sidebar);
72    startStickY = sRect.top + getScrollY() - topOffset;
73    lastLeft = sRect.left;
74    lastWidth = sRect.width;
75
76    // Admin-Bar berücksichtigen
77    topOffset = TOP_OFFSET_BASE + adminBarHeight();
78
79    info('computeStart → startStickY:', startStickY, 'left:', lastLeft, 'width:', lastWidth, 'topOffset (with adminbar):', topOffset);
80  }
81
82  function clampHeight(){
83    var scrollY = getScrollY();
84    var vpH = window.innerHeight || document.documentElement.clientHeight;
85    var byViewport = Math.max(0, vpH - topOffset);
86    var fTop = footerAbsTop();
87    var byFooter = Math.max(0, fTop - scrollY - topOffset - BOTTOM_GAP);
88    var allowed = Math.max(100, Math.min(byViewport, byFooter));
89
90    inner.style.maxHeight = allowed + 'px';
91    // internes Scrollen nur wenn nötig
92    // setTimeout → Style zuerst anwenden, dann Maße prüfen
93    setTimeout(function(){
94      var needs = inner.scrollHeight > inner.clientHeight + 1;
95      inner.style.overflowY = needs ? 'auto' : 'visible';
96      if (DEBUG) table([
97        {key:'scrollY', value: scrollY},
98        {key:'viewportH', value: vpH},
99        {key:'footerTopAbs', value: fTop},
100        {key:'allowed maxHeight', value: allowed},
101        {key:'inner.scrollHeight', value: inner.scrollHeight},
102        {key:'inner.clientHeight', value: inner.clientHeight},
103        {key:'needsScroll', value: needs},
104        {key:'isFixed', value: isFixed}
105      ]);
106    },0);
107  }
108
109  // -----------------------------
110  // Fixed mode
111  // -----------------------------
112  function enterFixed(){
113    if (isFixed) return;
114    isFixed = true;
115
116    // Platzhalter einfügen, damit die Spalte ihre Höhe behält
117    if (!placeholder){
118      placeholder = document.createElement('div');
119      placeholder.className = 'gt-sticky-sidebar-spacer';
120    }
121    placeholder.style.height = inner.getBoundingClientRect().height + 'px';
122    placeholder.style.width  = lastWidth + 'px';
123    sidebar.insertBefore(placeholder, inner);
124
125    // inner fixieren
126    inner.style.position = 'fixed';
127    inner.style.top = topOffset + 'px';
128    inner.style.left = lastLeft + 'px';
129    inner.style.width = lastWidth + 'px';
130    inner.style.boxSizing = 'border-box';
131
132    sidebar.classList.add('gt-fixed-active');
133    log('→ enterFixed');
134  }
135
136  function updateFixed(){
137    if (!isFixed) return;
138    // Bei Resize/Layout-Shift Left/Width nachziehen
139    var sRect = rect(sidebar);
140    lastLeft = sRect.left;
141    lastWidth = sRect.width;
142    inner.style.left = lastLeft + 'px';
143    inner.style.width = lastWidth + 'px';
144
145    // Spacer-Höhe an Inhalt anpassen (z.B. Ads)
146    placeholder.style.height = inner.getBoundingClientRect().height + 'px';
147    placeholder.style.width  = lastWidth + 'px';
148  }
149
150  function leaveFixed(){
151    if (!isFixed) return;
152    isFixed = false;
153
154    inner.style.position = 'static';
155    inner.style.top = '';
156    inner.style.left = '';
157    inner.style.width = '';
158    sidebar.classList.remove('gt-fixed-active');
159
160    if (placeholder && placeholder.parentNode === sidebar){
161      sidebar.removeChild(placeholder);
162    }
163    log('← leaveFixed');
164  }
165
166  // -----------------------------
167  // Scroll/resize tick
168  // -----------------------------
169  function tick(reason){
170    if (!mq.matches){
171      // reset auf Mobil/Tablet
172      leaveFixed();
173      inner.style.maxHeight = '';
174      inner.style.overflowY = '';
175      return;
176    }
177
178    // mit Adminbar neu kalkulieren (falls Login-Status/Stickybar wechselte)
179    topOffset = TOP_OFFSET_BASE + adminBarHeight();
180
181    var y = getScrollY();
182    var shouldFix = y >= startStickY;
183
184    if (shouldFix) enterFixed(); else leaveFixed();
185
186    clampHeight();
187
188    if (isFixed) updateFixed();
189
190    if (DEBUG) log('tick(', reason, ') → y:', y, 'startStickY:', startStickY, 'shouldFix:', shouldFix);
191  }
192
193  // -----------------------------
194  // Init
195  // -----------------------------
196  function initCore(){
197    // Inner-Wrapper bauen
198    inner = document.createElement('div');
199    inner.className = 'gt-sticky-sidebar-inner';
200    // move children
201    while (sidebar.firstChild) inner.appendChild(sidebar.firstChild);
202    sidebar.appendChild(inner);
203
204    // Basistyle für inner (nicht sticky, nur für Box-Sizing & Scrollbar)
205    var style = document.createElement('style');
206    style.textContent = [
207      '.gt-sticky-sidebar-inner{box-sizing:border-box;}',
208      '.gt-sticky-sidebar-inner::-webkit-scrollbar{width:10px;}',
209      '.gt-sticky-sidebar-inner::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2);border-radius:6px;}',
210      '.gt-fixed-active .gt-sticky-sidebar-inner{ /* marker class, no visual needed */ }'
211    ].join('\n');
212    document.head.appendChild(style);
213
214    mq = window.matchMedia(DESKTOP_MEDIA);
215
216    computeStart();
217    tick('init');
218
219    window.addEventListener('scroll', function(){ tick('scroll'); }, {passive:true});
220    window.addEventListener('resize', function(){
221      computeStart();
222      tick('resize');
223    }, {passive:true});
224
225    // Reagiere auf dynamische Größen (Ads/Widgets/Footer)
226    if ('ResizeObserver' in window){
227      ro = new ResizeObserver(function(){
228        computeStart();
229        tick('resize-observer');
230      });
231      ro.observe(inner);
232      ro.observe(sidebar);
233      if (footer) ro.observe(footer);
234    }
235
236    info('initialized (pure fixed mode). breakpoint:', DESKTOP_MEDIA, 'TOP_OFFSET_BASE:', TOP_OFFSET_BASE, 'BOTTOM_GAP:', BOTTOM_GAP);
237  }
238
239  function start(){
240    sidebar = document.querySelector(SIDEBAR_SELECTOR);
241    footer  = document.querySelector(FOOTER_SELECTOR) || null;
242
243    if (!sidebar){
244      warn('sidebar not found, polling…');
245      var tries = 0;
246      var t = setInterval(function(){
247        tries++;
248        sidebar = document.querySelector(SIDEBAR_SELECTOR);
249        if (sidebar){
250          clearInterval(t);
251          footer  = document.querySelector(FOOTER_SELECTOR) || null;
252          info('sidebar found after', tries, 'tries');
253          initCore();
254        } else if (tries >= INIT_POLL_MAX){
255          clearInterval(t);
256          warn('giving up, no sidebar after', tries, 'tries');
257        }
258      }, INIT_POLL_MS);
259      return;
260    }
261    initCore();
262  }
263
264  // DOM ready
265  if (document.readyState === 'loading') {
266    document.addEventListener('DOMContentLoaded', start);
267  } else {
268    start();
269  }
270})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.