PageSourceSearch

https://www.bannersnack.com/public/js/layout-stability.js

js bannersnack.com collected 2026-10-03 19:23:12 UTC 9,791 bytes, 266 lines download raw bytes

1/**
2 * Layout Stability Optimization
3 * Prevents Cumulative Layout Shift (CLS) by managing dynamic content loading
4 * and font loading optimization
5 */
6
7(function () {
8    'use strict';
9
10    // Font loading optimization
11    function optimizeFontLoading() {
12        // Add font loading class to body
13        document.body.classList.add('font-loading');
14
15        // Check if fonts are loaded
16        if (document.fonts && document.fonts.ready) {
17            document.fonts.ready.then(function () {
18                document.body.classList.remove('font-loading');
19                document.body.classList.add('font-loaded');
20            });
21        } else {
22            // Fallback for older browsers
23            setTimeout(function () {
24                document.body.classList.remove('font-loading');
25                document.body.classList.add('font-loaded');
26            }, 1000);
27        }
28    }
29
30    // Image loading optimization
31    function optimizeImageLoading() {
32        // Only add loading="lazy" to images that don't have it and are below the fold
33        const images = document.querySelectorAll('img:not([loading])');
34        images.forEach(function (img) {
35            // Only add lazy loading to images below the fold
36            if (img.getBoundingClientRect().top > window.innerHeight) {
37                img.setAttribute('loading', 'lazy');
38            }
39        });
40
41        // Handle image load events to prevent layout shifts
42        const allImages = document.querySelectorAll('img');
43        allImages.forEach(function (img) {
44            // Don't interfere with images that already have loading states
45            if (
46                img.classList.contains('loaded') ||
47                img.classList.contains('loading')
48            ) {
49                return;
50            }
51
52            if (img.complete) {
53                img.classList.add('loaded');
54            } else {
55                // Add loading class for images that are still loading
56                img.classList.add('loading');
57
58                img.addEventListener('load', function () {
59                    this.classList.remove('loading');
60                    this.classList.add('loaded');
61                });
62
63                img.addEventListener('error', function () {
64                    this.classList.remove('loading');
65                    this.classList.add('error');
66                });
67            }
68        });
69    }
70
71    // Banner gallery optimization
72    function optimizeBannerGallery() {
73        const galleries = document.querySelectorAll(
74            '.banner-gallery, .gallery-container'
75        );
76
77        galleries.forEach(function (gallery) {
78            // Only add loading state if gallery doesn't already have content
79            if (
80                gallery.children.length === 0 ||
81                gallery.classList.contains('gallery-loading')
82            ) {
83                gallery.classList.add('gallery-loading');
84
85                // Remove loading state when content is actually loaded
86                // This should be replaced with actual loading logic from the gallery component
87                const observer = new MutationObserver(function (mutations) {
88                    mutations.forEach(function (mutation) {
89                        if (
90                            mutation.type === 'childList' &&
91                            mutation.addedNodes.length > 0
92                        ) {
93                            gallery.classList.remove('gallery-loading');
94                            gallery.classList.add('gallery-loaded');
95                            observer.disconnect();
96                        }
97                    });
98                });
99
100                observer.observe(gallery, { childList: true });
101
102                // Fallback timeout in case content doesn't load
103                setTimeout(function () {
104                    if (gallery.classList.contains('gallery-loading')) {
105                        gallery.classList.remove('gallery-loading');
106                        gallery.classList.add('gallery-loaded');
107                        observer.disconnect();
108                    }
109                }, 2000);
110            }
111        });
112    }
113
114    // Dynamic content loading optimization
115    function optimizeDynamicContent() {
116        // Handle dynamic lists
117        const dynamicLists = document.querySelectorAll('.dynamic-list');
118        dynamicLists.forEach(function (list) {
119            list.style.minHeight = '200px';
120        });
121
122        // Handle dynamic grids
123        const dynamicGrids = document.querySelectorAll('.stable-grid');
124        dynamicGrids.forEach(function (grid) {
125            const items = grid.querySelectorAll('.grid-item');
126            items.forEach(function (item) {
127                item.style.minHeight = '200px';
128            });
129        });
130    }
131
132    // Prevent layout shifts from external content
133    function optimizeExternalContent() {
134        // Handle ads and external embeds
135        const adContainers = document.querySelectorAll(
136            '.ad-container, .ad-placeholder'
137        );
138        adContainers.forEach(function (container) {
139            container.style.minHeight = '250px';
140        });
141
142        // Handle iframes
143        const iframes = document.querySelectorAll('iframe');
144        iframes.forEach(function (iframe) {
145            if (!iframe.style.height) {
146                iframe.style.minHeight = '200px';
147            }
148        });
149    }
150
151    // Mobile-specific optimizations
152    function optimizeMobile() {
153        if (window.innerWidth <= 768) {
154            // Prevent horizontal scroll only if not already set
155            if (!document.body.style.overflowX) {
156                document.body.style.overflowX = 'hidden';
157            }
158
159            // Optimize touch targets only for elements that need it
160            const touchTargets = document.querySelectorAll(
161                'button:not(.skip-link), .btn:not(.skip-link), .cta-button:not(.skip-link), a:not(.skip-link)'
162            );
163            touchTargets.forEach(function (target) {
164                // Don't modify elements that already have proper sizing
165                if (target.style.minHeight || target.style.minWidth) {
166                    return;
167                }
168
169                const rect = target.getBoundingClientRect();
170                if (rect.height < 44 || rect.width < 44) {
171                    // Use CSS classes instead of inline styles to avoid specificity issues
172                    target.classList.add('touch-optimized');
173                }
174            });
175        }
176    }
177
178    // Container query polyfill for older browsers
179    function initContainerQueries() {
180        if (!CSS.supports('container-type: inline-size')) {
181            // Add fallback styles for browsers without container query support
182            const containers = document.querySelectorAll(
183                '.responsive-container'
184            );
185            containers.forEach(function (container) {
186                if (container.offsetWidth >= 768) {
187                    container.classList.add('container-wide');
188                } else {
189                    container.classList.add('container-narrow');
190                }
191            });
192        }
193    }
194
195    // Resize observer for responsive behavior
196    function initResizeObserver() {
197        if (window.ResizeObserver) {
198            const resizeObserver = new ResizeObserver(function (entries) {
199                entries.forEach(function (entry) {
200                    const element = entry.target;
201                    if (element.classList.contains('responsive-container')) {
202                        if (entry.contentRect.width >= 768) {
203                            element.classList.add('container-wide');
204                            element.classList.remove('container-narrow');
205                        } else {
206                            element.classList.add('container-narrow');
207                            element.classList.remove('container-wide');
208                        }
209                    }
210                });
211            });
212
213            // Observe responsive containers
214            const containers = document.querySelectorAll(
215                '.responsive-container'
216            );
217            containers.forEach(function (container) {
218                resizeObserver.observe(container);
219            });
220        }
221    }
222
223    // Initialize all optimizations
224    function init() {
225        // Only run optimizations if they haven't been applied already
226        if (!document.body.classList.contains('layout-stability-initialized')) {
227            optimizeFontLoading();
228            optimizeImageLoading();
229            optimizeBannerGallery();
230            optimizeDynamicContent();
231            optimizeExternalContent();
232            optimizeMobile();
233            initContainerQueries();
234            initResizeObserver();
235
236            // Mark as initialized to prevent duplicate runs
237            document.body.classList.add('layout-stability-initialized');
238        }
239    }
240
241    // Run on DOM ready
242    if (document.readyState === 'loading') {
243        document.addEventListener('DOMContentLoaded', init);
244    } else {
245        init();
246    }
247
248    // Re-run on window resize
249    let resizeTimeout;
250    window.addEventListener('resize', function () {
251        clearTimeout(resizeTimeout);
252        resizeTimeout = setTimeout(function () {
253            optimizeMobile();
254        }, 250);
255    });
256
257    // Expose functions for external use
258    window.LayoutStability = {
259        optimizeFontLoading: optimizeFontLoading,
260        optimizeImageLoading: optimizeImageLoading,
261        optimizeBannerGallery: optimizeBannerGallery,
262        optimizeDynamicContent: optimizeDynamicContent,
263        optimizeExternalContent: optimizeExternalContent,
264        optimizeMobile: optimizeMobile
265    };
266})();

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.