PageSourceSearch

https://buhl-berater.de/wp-content/themes/wp-themes-buhl-cobe-them…/header-product-bar/header-product-bar.js

js buhl-berater.de collected 2026-10-09 23:53:11 UTC 7,618 bytes, 254 lines download raw bytes

1export function initHeaderProductBar() {
2    const bar = document.querySelector('.header-product-bar');
3    const header = document.querySelector('header');
4
5    if (!bar || !header) {
6        return;
7    }
8
9    const slider = bar.querySelector('.header-product-bar__slider');
10    let splideInstance = null;
11
12    const mediaQuery = window.matchMedia('(max-width: 992px)');
13
14    const cleanupTrackVisibility = () => {
15        const track = slider ? slider.querySelector('.splide__track') : null;
16        if (!track) {
17            return;
18        }
19        track.removeAttribute('hidden');
20        track.removeAttribute('aria-hidden');
21        track.style.display = '';
22        track.style.visibility = '';
23        track.style.opacity = '';
24        track.style.height = '';
25    };
26
27    let splideRetryCount = 0;
28    const enableMobileSlider = () => {
29        if (!slider || splideInstance) {
30            return;
31        }
32
33        if (!window.Splide) {
34            if (splideRetryCount < 20) {
35                splideRetryCount += 1;
36                window.setTimeout(enableMobileSlider, 100);
37            }
38            return;
39        }
40
41        splideRetryCount = 0;
42        slider.classList.add('splide');
43        cleanupTrackVisibility();
44
45        splideInstance = new window.Splide(slider, {
46            type: 'slide',
47            perPage: 2.2,
48            gap: '12px',
49            pagination: false,
50            arrows: false,
51            drag: 'free',
52            focus: 0,
53            padding: { left: '16px', right: '16px' },
54            breakpoints: {
55                768: { perPage: 1.6 },
56                576: { perPage: 1.3 }
57            }
58        });
59        splideInstance.mount();
60    };
61
62    const disableMobileSlider = () => {
63        if (splideInstance) {
64            splideInstance.destroy(true);
65            splideInstance = null;
66        }
67        if (slider) {
68            slider.classList.remove('splide');
69        }
70        cleanupTrackVisibility();
71    };
72
73    const syncSlider = (matches) => {
74        if (matches) {
75            enableMobileSlider();
76        } else {
77            disableMobileSlider();
78        }
79    };
80
81    syncSlider(mediaQuery.matches);
82
83    if (mediaQuery.addEventListener) {
84        mediaQuery.addEventListener('change', (event) => syncSlider(event.matches));
85    } else if (mediaQuery.addListener) {
86        mediaQuery.addListener((event) => syncSlider(event.matches));
87    }
88
89    const links = bar.querySelectorAll('.header-product-bar__item');
90    links.forEach((link) => {
91        link.addEventListener('click', () => {
92            links.forEach((item) => {
93                item.classList.remove('is-active');
94                item.removeAttribute('aria-current');
95            });
96            link.classList.add('is-active');
97            link.setAttribute('aria-current', 'true');
98        });
99    });
100
101    // showThreshold: how close to top the user must scroll to reveal the bar again.
102    const showThreshold = 2;
103    let isHidden = header.classList.contains('header-product-bar-hidden');
104    let isTicking = false;
105    let isAnchorNavigating = false;
106    let anchorNavigationTimer = null;
107
108    // Measures the full outer height of the bar including CSS margins.
109    const getBarOuterHeight = () => {
110        const style = window.getComputedStyle(bar);
111        const marginTop = parseFloat(style.marginTop) || 0;
112        const marginBottom = parseFloat(style.marginBottom) || 0;
113        return bar.offsetHeight + marginTop + marginBottom;
114    };
115
116    let barOuterHeight = getBarOuterHeight();
117
118    // Keep the threshold below the bar so it remains hidden until the user returns to the top.
119    let hideThreshold = barOuterHeight + showThreshold + 16;
120
121    const recalculate = () => {
122        barOuterHeight = getBarOuterHeight();
123        hideThreshold = barOuterHeight + showThreshold + 16;
124    };
125
126    const suspendAutoToggleForAnchorNavigation = (ms = 900) => {
127        isAnchorNavigating = true;
128        if (anchorNavigationTimer) {
129            window.clearTimeout(anchorNavigationTimer);
130        }
131
132        anchorNavigationTimer = window.setTimeout(() => {
133            isAnchorNavigating = false;
134            updateScrollState();
135        }, ms);
136    };
137
138    const isSamePageAnchorUrl = (href) => {
139        if (!href) {
140            return false;
141        }
142
143        let parsed;
144        try {
145            parsed = new URL(href, window.location.href);
146        } catch (error) {
147            return false;
148        }
149
150        if (!parsed.hash) {
151            return false;
152        }
153
154        return parsed.pathname === window.location.pathname && parsed.search === window.location.search;
155    };
156
157    const getAnchorTarget = (hash) => {
158        if (!hash) {
159            return null;
160        }
161
162        const rawId = hash.replace(/^#/, '');
163        if (!rawId) {
164            return null;
165        }
166
167        let decodedId = rawId;
168        try {
169            decodedId = decodeURIComponent(rawId);
170        } catch (error) {
171            decodedId = rawId;
172        }
173
174        return document.getElementById(decodedId);
175    };
176
177    const updateScrollState = () => {
178        if (isAnchorNavigating) {
179            return;
180        }
181
182        const scrollY = window.scrollY;
183
184        if (!isHidden && scrollY >= hideThreshold) {
185            header.classList.add('header-product-bar-hidden');
186            isHidden = true;
187            return;
188        }
189
190        if (isHidden && scrollY <= showThreshold) {
191            header.classList.remove('header-product-bar-hidden');
192            isHidden = false;
193        }
194    };
195
196    recalculate();
197    updateScrollState();
198
199    links.forEach((link) => {
200        link.addEventListener('click', (event) => {
201            const href = link.getAttribute('href') || '';
202            if (!isSamePageAnchorUrl(href)) {
203                return;
204            }
205
206            const parsedUrl = new URL(href, window.location.href);
207            const target = getAnchorTarget(parsedUrl.hash);
208            if (!target) {
209                return;
210            }
211
212            event.preventDefault();
213            suspendAutoToggleForAnchorNavigation();
214
215            // During anchor navigation we collapse the bar immediately,
216            // then scroll smoothly with sticky-header offset.
217            if (!isHidden) {
218                header.classList.add('header-product-bar-hidden');
219                isHidden = true;
220            }
221
222            window.requestAnimationFrame(() => {
223                const stickyOffset = header.offsetHeight + 8;
224                const targetY = target.getBoundingClientRect().top + window.scrollY - stickyOffset;
225
226                window.scrollTo({
227                    top: Math.max(0, targetY),
228                    behavior: 'smooth'
229                });
230
231                if (window.history && typeof window.history.replaceState === 'function') {
232                    window.history.replaceState(null, '', parsedUrl.hash);
233                }
234            });
235        });
236    });
237
238    window.addEventListener('scroll', () => {
239        if (isTicking) {
240            return;
241        }
242
243        isTicking = true;
244        window.requestAnimationFrame(() => {
245            updateScrollState();
246            isTicking = false;
247        });
248    }, { passive: true });
249
250    window.addEventListener('resize', () => {
251        recalculate();
252        updateScrollState();
253    }, { passive: true });
254}

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.