@madj2k/fe-frontend-kit 2.0.7 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -0
- package/menus/flyout-menu/flyout-menu-2.0.js +4 -1
- package/package.json +1 -1
- package/readme.md +138 -3
- package/tools/banner/banner-2.0.js +89 -71
- package/tools/banner/banner-2.0.scss +1 -8
- package/tools/better-resize-event/better-resize-event-2.0.js +136 -0
- package/tools/better-resize-event/better-resize-event-2.0.scss +0 -0
- package/tools/better-resize-event/index.js +2 -0
- package/tools/better-resize-event/index.scss +1 -0
- package/tools/owl-thumbnail/owl-thumbnail-2.0.js +21 -22
- package/tools/resize-end/index.js +6 -2
- package/tools/resize-end/index.scss +1 -1
- package/tools/scrolling/scrolling-2.0.js +58 -36
- package/tools/toggled-overlay/toggled-overlay-2.0.js +5 -5
- package/tools/resize-end/resize-end-2.0.js +0 -108
- package/tools/resize-end/resize-end-2.0.scss +0 -10
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Tools
|
|
2
2
|
export { Madj2kBanner } from './tools/banner';
|
|
3
|
+
export { Madj2kBetterResizeEvent } from './tools/better-resize-event';
|
|
3
4
|
export { Madj2kOwlThumbnail } from './tools/owl-thumbnail';
|
|
4
5
|
export { Madj2kResizeEnd } from './tools/resize-end';
|
|
5
6
|
export { Madj2kScrolling } from './tools/scrolling';
|
|
@@ -334,12 +334,15 @@ class Madj2kFlyoutMenu {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
let height = this.settings.$menuInner.offsetHeight || this.settings.$menu.offsetHeight;
|
|
337
|
+
const refObj = this.settings.$positionReference || this.$element;
|
|
338
|
+
const refPos = refObj.getBoundingClientRect();
|
|
339
|
+
const flyoutTop = refPos.top + refObj.offsetHeight;
|
|
337
340
|
|
|
338
341
|
// heightMode "full" with deprecated fullHeight-setting as fallback
|
|
339
342
|
if (this.settings.heightMode === 'full' || this.settings.fullHeight === true) {
|
|
340
343
|
const viewPortHeight = window.innerHeight;
|
|
341
344
|
if (height < viewPortHeight) {
|
|
342
|
-
this.settings.$menu.style.height = `100vh`;
|
|
345
|
+
this.settings.$menu.style.height = `calc(100vh + ${flyoutTop}px)`;
|
|
343
346
|
} else {
|
|
344
347
|
this.settings.$menu.style.height = `${height}px`;
|
|
345
348
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -30,6 +30,45 @@ Each menu component can be used separately.
|
|
|
30
30
|
A lightweight class to show a full-page overlay (banner, popup, hint or cookie layer),
|
|
31
31
|
with opening and closing animation and optional cookie persistence.
|
|
32
32
|
|
|
33
|
+
Init:
|
|
34
|
+
```
|
|
35
|
+
import { Madj2kBanner } from '@madj2k/frontend-kit/tools/banner';
|
|
36
|
+
const banner = new Madj2kBanner({
|
|
37
|
+
bannerId: 'my-banner',
|
|
38
|
+
activeClass: 'active',
|
|
39
|
+
openClass: 'open',
|
|
40
|
+
closingClass: 'closing',
|
|
41
|
+
openingClass: 'opening',
|
|
42
|
+
timeout: 1000,
|
|
43
|
+
cookieName: 'myBannerCookie',
|
|
44
|
+
cookieDays: 30,
|
|
45
|
+
debug: false
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
HTML:
|
|
50
|
+
```
|
|
51
|
+
<div id="my-banner" class="banner my-banner">
|
|
52
|
+
<div class="my-banner-content">
|
|
53
|
+
<button class="my-banner-close" aria-controls="my-banner">Close</button>
|
|
54
|
+
<p>Your overlay content here ...</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
CSS:
|
|
60
|
+
```
|
|
61
|
+
.my-banner {
|
|
62
|
+
position: fixed;
|
|
63
|
+
top: 0;
|
|
64
|
+
left: 0;
|
|
65
|
+
width: 100%;
|
|
66
|
+
height: 100%;
|
|
67
|
+
background: rgba(0, 0, 0, 0.8);
|
|
68
|
+
z-index: 9999;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
33
72
|
# JS: OWL-Thumbnails
|
|
34
73
|
A JavaScript helper class to create a main OWL carousel with a synchronized thumbnail navigation carousel.
|
|
35
74
|
|
|
@@ -45,12 +84,67 @@ including:
|
|
|
45
84
|
This is especially useful for image galleries or product carousels in CMS setups (e.g. TYPO3, WordPress, etc.)
|
|
46
85
|
where content may change dynamically.
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
Init with available options:
|
|
88
|
+
```
|
|
89
|
+
const owlThumbnail = new Madj2kOwlThumbnail('.js-main-carousel', '.js-thumbs-carousel', {
|
|
90
|
+
main: {
|
|
91
|
+
items: 1,
|
|
92
|
+
margin: 20,
|
|
93
|
+
dots: true,
|
|
94
|
+
nav: true,
|
|
95
|
+
autoHeight: true
|
|
96
|
+
},
|
|
97
|
+
thumbs: {
|
|
98
|
+
items: 3,
|
|
99
|
+
margin: 10,
|
|
100
|
+
dots: false,
|
|
101
|
+
nav: true,
|
|
102
|
+
center: true
|
|
103
|
+
},
|
|
104
|
+
resizeEvent: 'custom.resize',
|
|
105
|
+
equalizeThumbHeights: true,
|
|
106
|
+
noStageOffset: true
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
HTML:
|
|
112
|
+
```
|
|
113
|
+
<div class="js-main-carousel owl-carousel">
|
|
114
|
+
<div class="item"><img src="image1.jpg" alt=""></div>
|
|
115
|
+
<div class="item"><img src="image2.jpg" alt=""></div>
|
|
116
|
+
<div class="item"><img src="image3.jpg" alt=""></div>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="js-thumbs-carousel owl-carousel">
|
|
119
|
+
<div class="item" data-index="0"><img src="thumb1.jpg" alt=""></div>
|
|
120
|
+
<div class="item" data-index="1"><img src="thumb2.jpg" alt=""></div>
|
|
121
|
+
<div class="item" data-index="2"><img src="thumb3.jpg" alt=""></div>
|
|
122
|
+
</div>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
# JS: Better Resize Event
|
|
126
|
+
A lightweight helper class that triggers a debounced 'madj2k-better-resize-event' event
|
|
50
127
|
when the user finishes resizing the browser window.
|
|
51
128
|
|
|
52
129
|
It also manages a scrolling detection state (via body attribute) to avoid triggering
|
|
53
|
-
resize-
|
|
130
|
+
resize-events during user scrolling on mobile, and respects active input fields (keyboard-issue on mobile).
|
|
131
|
+
|
|
132
|
+
Init with available options:
|
|
133
|
+
```
|
|
134
|
+
import { Madj2kBetterResizeEvent } from '@madj2k/frontend-kit/tools/better-resize-event';
|
|
135
|
+
const betterResizeEvent = new Madj2kBetterResizeEvent({
|
|
136
|
+
resizeEndTimeout: 300,
|
|
137
|
+
scrollingEndTimeout: 600,
|
|
138
|
+
viewportDeltaThreshold: 50
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
Usage:
|
|
142
|
+
```
|
|
143
|
+
document.addEventListener('madj2k-better-resize-event', () => {
|
|
144
|
+
console.log('Resize fired');
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
54
148
|
|
|
55
149
|
# JS: Scrolling
|
|
56
150
|
A lightweight scrolling helper class that enables:
|
|
@@ -62,11 +156,52 @@ A lightweight scrolling helper class that enables:
|
|
|
62
156
|
The class is fully configurable via options and is designed to be used in CMS contexts
|
|
63
157
|
where elements can be added, removed or re-ordered dynamically.
|
|
64
158
|
|
|
159
|
+
Init with available options:
|
|
160
|
+
```
|
|
161
|
+
import { Madj2kScrolling } from '@madj2k/frontend-kit/tools/scrolling';
|
|
162
|
+
const scrolling = new Madj2kScrolling({
|
|
163
|
+
anchorScrollingCollapsibleSelector: ['.collapse', '.custom-collapse'],
|
|
164
|
+
anchorScrollingSelector: ['a[href^="#"]', '.btn-scroll'],
|
|
165
|
+
anchorScrollingOffsetSelector: '#siteheader',
|
|
166
|
+
anchorScrollingDisableSelector: '.js-no-scroll',
|
|
167
|
+
appearOnScrollSelector: ['.js-appear-on-scroll'],
|
|
168
|
+
appearOnScrollTimeout: 500,
|
|
169
|
+
appearOnScrollThreshold: 25,
|
|
170
|
+
debug: true
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
Usage with Appear-On-Scroll (HTML):
|
|
174
|
+
```
|
|
175
|
+
<div class="js-appear-on-scroll">
|
|
176
|
+
<h2>Animated content</h2>
|
|
177
|
+
<p>This will fade and move in when scrolled into view.</p>
|
|
178
|
+
</div>
|
|
179
|
+
```
|
|
180
|
+
|
|
65
181
|
# JS: Toggled Overlay
|
|
66
182
|
This class toggles the visibility of any target element referenced by the `aria-controls`
|
|
67
183
|
attribute of a trigger element (button, link, etc.). It manages ARIA attributes for accessibility
|
|
68
184
|
and allows overlays to be closed externally via a custom event.
|
|
69
185
|
|
|
186
|
+
Init with available options:
|
|
187
|
+
```
|
|
188
|
+
import { Madj2kToggledOverlay } from '@madj2k/frontend-kit/tools/toggled-overlay';
|
|
189
|
+
const overlayToggle = new Madj2kToggledOverlay('.js-toggled-overlay');
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
HTML:
|
|
193
|
+
```
|
|
194
|
+
<button class="js-toggled-overlay toggled-overlay-button"
|
|
195
|
+
aria-label="Open overlay"
|
|
196
|
+
aria-controls="my-overlay"
|
|
197
|
+
aria-expanded="false">
|
|
198
|
+
<span class="icon icon-info"></span>
|
|
199
|
+
</button>
|
|
200
|
+
|
|
201
|
+
<div id="my-overlay" class="toggled-overlay" aria-hidden="true">
|
|
202
|
+
<!-- Overlay content -->
|
|
203
|
+
</div>
|
|
204
|
+
```
|
|
70
205
|
|
|
71
206
|
# Flyout-Navigation
|
|
72
207
|
## Usage
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Overlay (Vanilla JS)
|
|
2
|
+
* Banner / Overlay (Vanilla JS)
|
|
3
3
|
*
|
|
4
4
|
* A lightweight class to show a full-page overlay (banner, popup, hint or cookie layer),
|
|
5
5
|
* with opening and closing animation and optional cookie persistence.
|
|
@@ -15,23 +15,33 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
17
17
|
* @copyright 2025 Steffen Kroggel
|
|
18
|
-
* @version 2.0.
|
|
18
|
+
* @version 2.0.1 – legacy bannerCloseId fallback
|
|
19
19
|
* @license GNU General Public License v3.0
|
|
20
20
|
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
21
21
|
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Advanced usage with config:
|
|
24
|
+
* const banner = new Madj2kBanner({
|
|
25
|
+
* bannerId: 'my-banner',
|
|
26
|
+
* activeClass: 'active',
|
|
27
|
+
* openClass: 'open',
|
|
28
|
+
* closingClass: 'closing',
|
|
29
|
+
* openingClass: 'opening',
|
|
30
|
+
* timeout: 1000,
|
|
31
|
+
* cookieName: 'myBannerCookie',
|
|
32
|
+
* cookieDays: 30,
|
|
33
|
+
* debug: false
|
|
34
|
+
* });
|
|
22
35
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* <div id="my-banner" class="my-banner">
|
|
36
|
+
* @example
|
|
37
|
+
* <div id="my-banner" class="banner my-banner">
|
|
26
38
|
* <div class="my-banner-content">
|
|
27
|
-
* <button
|
|
39
|
+
* <button class="my-banner-close" aria-controls="my-banner">Close</button>
|
|
28
40
|
* <p>Your overlay content here ...</p>
|
|
29
41
|
* </div>
|
|
30
42
|
* </div>
|
|
31
43
|
*
|
|
32
|
-
*
|
|
33
|
-
* Example CSS:
|
|
34
|
-
*
|
|
44
|
+
* @example
|
|
35
45
|
* .my-banner {
|
|
36
46
|
* position: fixed;
|
|
37
47
|
* top: 0;
|
|
@@ -39,58 +49,23 @@
|
|
|
39
49
|
* width: 100%;
|
|
40
50
|
* height: 100%;
|
|
41
51
|
* background: rgba(0, 0, 0, 0.8);
|
|
42
|
-
* opacity: 0;
|
|
43
|
-
* visibility: hidden;
|
|
44
|
-
* transition: opacity 0.5s ease, visibility 0.5s ease;
|
|
45
52
|
* z-index: 9999;
|
|
46
53
|
* }
|
|
47
|
-
*
|
|
48
|
-
* .my-banner.active {
|
|
49
|
-
* visibility: visible;
|
|
50
|
-
* }
|
|
51
|
-
*
|
|
52
|
-
* .my-banner.open {
|
|
53
|
-
* opacity: 1;
|
|
54
|
-
* }
|
|
55
|
-
*
|
|
56
|
-
* .my-banner.closing {
|
|
57
|
-
* opacity: 0;
|
|
58
|
-
* }
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* Basic usage:
|
|
62
|
-
*
|
|
63
|
-
* const banner = new Madj2kBanner();
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* Advanced usage with config:
|
|
67
|
-
*
|
|
68
|
-
* const overlay = new Madj2kBanner({
|
|
69
|
-
* overlayId: 'my-banner',
|
|
70
|
-
* overlayCloseId: 'my-banner-close',
|
|
71
|
-
* activeClass: 'active',
|
|
72
|
-
* openClass: 'open',
|
|
73
|
-
* closingClass: 'closing',
|
|
74
|
-
* openingClass: 'opening',
|
|
75
|
-
* timeout: 1000,
|
|
76
|
-
* cookieName: 'myOverlayCookie',
|
|
77
|
-
* cookieDays: 30
|
|
78
|
-
* });
|
|
79
|
-
*
|
|
80
54
|
*/
|
|
55
|
+
|
|
81
56
|
class Madj2kBanner {
|
|
82
57
|
|
|
83
58
|
config = {
|
|
84
|
-
'
|
|
85
|
-
'
|
|
59
|
+
'bannerId' : 'banner',
|
|
60
|
+
'bannerCloseId': 'banner-close', // fallback legacy
|
|
86
61
|
'activeClass': 'active',
|
|
87
62
|
'openClass': 'open',
|
|
88
63
|
'closingClass': 'closing',
|
|
89
64
|
'openingClass': 'opening',
|
|
90
65
|
'timeout': '1000',
|
|
91
|
-
'cookieName': '
|
|
92
|
-
'cookieDays': '365'
|
|
93
|
-
|
|
66
|
+
'cookieName': 'banner',
|
|
67
|
+
'cookieDays': '365',
|
|
68
|
+
'debug': false
|
|
94
69
|
};
|
|
95
70
|
|
|
96
71
|
/**
|
|
@@ -99,44 +74,76 @@ class Madj2kBanner {
|
|
|
99
74
|
*/
|
|
100
75
|
constructor(config) {
|
|
101
76
|
this.config = {...this.config, ...config }
|
|
102
|
-
this.
|
|
77
|
+
this._log('Init Madj2kBanner');
|
|
78
|
+
this.initBanner();
|
|
103
79
|
}
|
|
104
80
|
|
|
105
81
|
/**
|
|
106
|
-
* Init
|
|
82
|
+
* Init banner if no cookie is set!
|
|
107
83
|
*/
|
|
108
|
-
|
|
109
|
-
const
|
|
84
|
+
initBanner () {
|
|
85
|
+
const banner = document.getElementById(this.config.bannerId);
|
|
110
86
|
const self = this;
|
|
111
87
|
|
|
112
|
-
if (
|
|
88
|
+
if (banner && ! this.getCookie()) {
|
|
89
|
+
this._log('No cookie found, showing banner');
|
|
113
90
|
|
|
114
|
-
|
|
91
|
+
banner.classList.add(self.config.activeClass);
|
|
115
92
|
setTimeout(function(){
|
|
116
|
-
|
|
93
|
+
banner.classList.add(self.config.openingClass);
|
|
117
94
|
setTimeout(function(){
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
banner.classList.add(self.config.openClass);
|
|
96
|
+
banner.classList.remove(self.config.openingClass);
|
|
97
|
+
self._log('Banner opened');
|
|
120
98
|
}, self.config.timeout);
|
|
121
|
-
}, 1 );
|
|
99
|
+
}, 1 );
|
|
122
100
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
overlayClose.addEventListener('click', (ee) => {
|
|
101
|
+
// Add close button(s) via aria-controls
|
|
102
|
+
let bannerCloseButtons = document.querySelectorAll('[aria-controls="' + this.config.bannerId + '"]');
|
|
126
103
|
|
|
127
|
-
|
|
104
|
+
// fallback to legacy ID
|
|
105
|
+
if (bannerCloseButtons.length === 0 && this.config.bannerCloseId) {
|
|
106
|
+
const legacyButton = document.getElementById(this.config.bannerCloseId);
|
|
107
|
+
if (legacyButton) {
|
|
108
|
+
this._log('Legacy close button fallback used:', this.config.bannerCloseId);
|
|
109
|
+
legacyButton.addEventListener('click', (ee) => {
|
|
110
|
+
this._closeBanner(banner);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
128
114
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
overlay.classList.remove(self.config.activeClass);
|
|
134
|
-
}, self.config.timeout);
|
|
115
|
+
bannerCloseButtons.forEach(button => {
|
|
116
|
+
button.addEventListener('click', (ee) => {
|
|
117
|
+
this._closeBanner(banner);
|
|
118
|
+
});
|
|
135
119
|
});
|
|
136
120
|
}
|
|
121
|
+
|
|
122
|
+
} else {
|
|
123
|
+
if (banner) {
|
|
124
|
+
this._log('Banner not shown (cookie found)');
|
|
125
|
+
}
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Closes the banner and sets cookie
|
|
131
|
+
* @param banner
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
_closeBanner(banner) {
|
|
135
|
+
this.setCookie();
|
|
136
|
+
|
|
137
|
+
banner.classList.add(this.config.closingClass);
|
|
138
|
+
banner.classList.remove(this.config.openClass);
|
|
139
|
+
this._log('Banner closing');
|
|
140
|
+
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
banner.classList.remove(this.config.closingClass);
|
|
143
|
+
banner.classList.remove(this.config.activeClass);
|
|
144
|
+
this._log('Banner closed');
|
|
145
|
+
}, this.config.timeout);
|
|
146
|
+
}
|
|
140
147
|
|
|
141
148
|
/**
|
|
142
149
|
* Sets cookie
|
|
@@ -146,9 +153,9 @@ class Madj2kBanner {
|
|
|
146
153
|
d.setTime(d.getTime() + (this.config.cookieDays * 24 * 60 * 60 * 1000));
|
|
147
154
|
let expires = "expires=" + d.toUTCString();
|
|
148
155
|
document.cookie = (this.config.cookieName + '=1;' + expires + ';path=/' + '; SameSite=Strict');
|
|
156
|
+
this._log('Cookie set:', this.config.cookieName);
|
|
149
157
|
}
|
|
150
158
|
|
|
151
|
-
|
|
152
159
|
/**
|
|
153
160
|
* Gets cookie values
|
|
154
161
|
* @returns {string}
|
|
@@ -163,10 +170,21 @@ class Madj2kBanner {
|
|
|
163
170
|
c = c.substring(1);
|
|
164
171
|
}
|
|
165
172
|
if (c.indexOf(name) === 0) {
|
|
173
|
+
this._log('Cookie found:', this.config.cookieName);
|
|
166
174
|
return c.substring(name.length, c.length);
|
|
167
175
|
}
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
return '';
|
|
171
179
|
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Debug logging helper
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
_log(...args) {
|
|
186
|
+
if (this.config.debug) {
|
|
187
|
+
console.log('[Madj2kBanner]', ...args);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
172
190
|
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
.
|
|
2
|
-
position: fixed;
|
|
3
|
-
top: 0;
|
|
4
|
-
left: 0;
|
|
5
|
-
width: 100%;
|
|
6
|
-
height: 100%;
|
|
7
|
-
background: rgba(0, 0, 0, 0.8);
|
|
1
|
+
.banner {
|
|
8
2
|
opacity: 0;
|
|
9
3
|
visibility: hidden;
|
|
10
4
|
transition: opacity 0.5s ease, visibility 0.5s ease;
|
|
11
|
-
z-index: 9999;
|
|
12
5
|
|
|
13
6
|
&.active {
|
|
14
7
|
visibility: visible;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Madj2kBetterResizeEvent
|
|
3
|
+
*
|
|
4
|
+
* A lightweight helper class that triggers a debounced 'madj2k-better-resize-event' event
|
|
5
|
+
* when the user finishes resizing the browser window.
|
|
6
|
+
*
|
|
7
|
+
* In some cases, you need to be able to react to the window's resize event. Unfortunately, however, the resize event is not only triggered at the end of the
|
|
8
|
+
* resizing of the browser window, but every time the size is changed in between. This can lead to an overhead if the event is handled every
|
|
9
|
+
* the event every time. With the JS-module, a final event is only triggered when the browser window has been resized.
|
|
10
|
+
*
|
|
11
|
+
* The script also considers edge-cases such as showing the software-keyboard and showing / hiding the navigation bar on mobile devices
|
|
12
|
+
* (both trigger a resize event).
|
|
13
|
+
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - On iOS (Safari), viewport height and width can change during scroll bounce or keyboard animation.
|
|
16
|
+
* - This version adds "viewport delta" detection to prevent false-positive resize events.
|
|
17
|
+
* - Scrolling state is now internal (no more data-resizeend-scrolling attribute).
|
|
18
|
+
* - New event: 'madj2k-better-resize-event' (legacy event still supported).
|
|
19
|
+
*
|
|
20
|
+
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
21
|
+
* @copyright 2025 Steffen Kroggel
|
|
22
|
+
* @version 2.0.1
|
|
23
|
+
* @license GNU General Public License v3.0
|
|
24
|
+
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Initialize with defaults
|
|
28
|
+
* const betterResizeEvent = new Madj2kBetterResizeEvent();
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Initialize with custom config
|
|
32
|
+
* const betterResizeEvent = new Madj2kBetterResizeEvent({
|
|
33
|
+
* resizeEndTimeout: 300,
|
|
34
|
+
* scrollingEndTimeout: 600,
|
|
35
|
+
* viewportDeltaThreshold: 50
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Listen to resize-end event
|
|
40
|
+
* document.addEventListener('madj2k-better-resize-event', () => {
|
|
41
|
+
* console.log('Resize fired');
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
class Madj2kBetterResizeEvent {
|
|
46
|
+
|
|
47
|
+
config = {
|
|
48
|
+
scrollingEndTimeout: 500,
|
|
49
|
+
resizeEndTimeout: 200,
|
|
50
|
+
viewportDeltaThreshold: 50 // px threshold to filter small height/width changes (default: 50)
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
finalEventTimers = {};
|
|
54
|
+
lastViewportHeight = window.innerHeight;
|
|
55
|
+
lastViewportWidth = window.innerWidth;
|
|
56
|
+
isScrolling = false;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Constructor
|
|
60
|
+
* @param {Object} config - Optional config overrides
|
|
61
|
+
*/
|
|
62
|
+
constructor(config = {}) {
|
|
63
|
+
this.config = { ...this.config, ...config };
|
|
64
|
+
|
|
65
|
+
this.lastViewportHeight = window.innerHeight;
|
|
66
|
+
this.lastViewportWidth = window.innerWidth;
|
|
67
|
+
this.isScrolling = false;
|
|
68
|
+
|
|
69
|
+
this.initScrollingDetection();
|
|
70
|
+
this.initResizeEvent();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Init resize event
|
|
75
|
+
*/
|
|
76
|
+
initResizeEvent() {
|
|
77
|
+
window.addEventListener('resize', () => {
|
|
78
|
+
const currentHeight = window.innerHeight;
|
|
79
|
+
const currentWidth = window.innerWidth;
|
|
80
|
+
|
|
81
|
+
const deltaH = Math.abs(this.lastViewportHeight - currentHeight);
|
|
82
|
+
const deltaW = Math.abs(this.lastViewportWidth - currentWidth);
|
|
83
|
+
|
|
84
|
+
// Skip if height/width change is below threshold (keyboard open/close or bounce)
|
|
85
|
+
if (deltaH < this.config.viewportDeltaThreshold && deltaW < this.config.viewportDeltaThreshold) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.lastViewportHeight = currentHeight;
|
|
90
|
+
this.lastViewportWidth = currentWidth;
|
|
91
|
+
|
|
92
|
+
if (!this.isScrolling) {
|
|
93
|
+
this.waitForFinalEvent(() => {
|
|
94
|
+
// Skip if input is focused (e.g. keyboard open on mobile)
|
|
95
|
+
const active = document.activeElement;
|
|
96
|
+
if (!(active && active.tagName === 'INPUT')) {
|
|
97
|
+
|
|
98
|
+
// New event
|
|
99
|
+
const newEvent = new CustomEvent('madj2k-better-resize-event');
|
|
100
|
+
document.dispatchEvent(newEvent);
|
|
101
|
+
|
|
102
|
+
// Legacy event for backwards compatibility
|
|
103
|
+
const legacyEvent = new CustomEvent('madj2k-resize-end');
|
|
104
|
+
document.dispatchEvent(legacyEvent);
|
|
105
|
+
}
|
|
106
|
+
}, this.config.resizeEndTimeout, 'resize');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Init scrolling detection
|
|
113
|
+
*/
|
|
114
|
+
initScrollingDetection() {
|
|
115
|
+
const handler = () => {
|
|
116
|
+
this.isScrolling = true;
|
|
117
|
+
|
|
118
|
+
this.waitForFinalEvent(() => {
|
|
119
|
+
this.isScrolling = false;
|
|
120
|
+
}, this.config.scrollingEndTimeout, 'scrolling');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
window.addEventListener('scroll', handler, { passive: true });
|
|
124
|
+
window.addEventListener('touchmove', handler, { passive: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Debounced final event dispatcher
|
|
129
|
+
*/
|
|
130
|
+
waitForFinalEvent(callback, ms, uniqueId = "default") {
|
|
131
|
+
if (this.finalEventTimers[uniqueId]) {
|
|
132
|
+
clearTimeout(this.finalEventTimers[uniqueId]);
|
|
133
|
+
}
|
|
134
|
+
this.finalEventTimers[uniqueId] = setTimeout(callback, ms);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@forward './better-resize-event-2.0';
|
|
@@ -21,6 +21,27 @@
|
|
|
21
21
|
* @license GNU General Public License v3.0
|
|
22
22
|
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
23
23
|
*
|
|
24
|
+
* @example:
|
|
25
|
+
* const owlThumbnail = new Madj2kOwlThumbnail('.js-main-carousel', '.js-thumbs-carousel', {
|
|
26
|
+
* main: {
|
|
27
|
+
* items: 1,
|
|
28
|
+
* margin: 20,
|
|
29
|
+
* dots: true,
|
|
30
|
+
* nav: true,
|
|
31
|
+
* autoHeight: true
|
|
32
|
+
* },
|
|
33
|
+
* thumbs: {
|
|
34
|
+
* items: 3,
|
|
35
|
+
* margin: 10,
|
|
36
|
+
* dots: false,
|
|
37
|
+
* nav: true,
|
|
38
|
+
* center: true
|
|
39
|
+
* },
|
|
40
|
+
* resizeEvent: 'custom.resize',
|
|
41
|
+
* equalizeThumbHeights: true,
|
|
42
|
+
* noStageOffset: true
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
24
45
|
* HTML example without data attributes:
|
|
25
46
|
* <div class="js-main-carousel owl-carousel">
|
|
26
47
|
* <div class="item"><img src="image1.jpg" alt=""></div>
|
|
@@ -45,28 +66,6 @@
|
|
|
45
66
|
* <div class="item" data-index="2"><img src="thumb3.jpg" alt=""></div>
|
|
46
67
|
* </div>
|
|
47
68
|
*
|
|
48
|
-
* @example:
|
|
49
|
-
*
|
|
50
|
-
* const owlThumbnail = new Madj2kOwlThumbnail('.js-main-carousel', '.js-thumbs-carousel', {
|
|
51
|
-
* main: {
|
|
52
|
-
* items: 1,
|
|
53
|
-
* margin: 20,
|
|
54
|
-
* dots: true,
|
|
55
|
-
* nav: true,
|
|
56
|
-
* autoHeight: true
|
|
57
|
-
* },
|
|
58
|
-
* thumbs: {
|
|
59
|
-
* items: 3,
|
|
60
|
-
* margin: 10,
|
|
61
|
-
* dots: false,
|
|
62
|
-
* nav: true,
|
|
63
|
-
* center: true
|
|
64
|
-
* },
|
|
65
|
-
* resizeEvent: 'custom.resize',
|
|
66
|
-
* equalizeThumbHeights: true,
|
|
67
|
-
* noStageOffset: true
|
|
68
|
-
* }
|
|
69
|
-
* });
|
|
70
69
|
*/
|
|
71
70
|
class Madj2kOwlThumbnail {
|
|
72
71
|
constructor(mainSelector, thumbSelector, options = {}, debug = false) {
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Legacy Wrapper: ResizeEnd
|
|
3
|
+
*/
|
|
4
|
+
import Madj2kBetterResizeEvent from '../better-resize-event/better-resize-event-2.0.js';
|
|
5
|
+
class Madj2kResizeEnd extends Madj2kBetterResizeEvent {}
|
|
6
|
+
export { Madj2kResizeEnd };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@forward '
|
|
1
|
+
@forward '../better-resize-event/index';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scrolling-Events
|
|
2
|
+
* Scrolling-Events
|
|
3
3
|
*
|
|
4
4
|
* A lightweight scrolling helper class that enables:
|
|
5
5
|
* 1. Body classes based on scroll direction (scroll-up / scroll-down)
|
|
@@ -12,24 +12,25 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
14
14
|
* @copyright 2025 Steffen Kroggel
|
|
15
|
-
* @version 2.0.
|
|
15
|
+
* @version 2.0.1 – Added debug option + log helper, changed anchorScrollingDisableSelector
|
|
16
16
|
* @license GNU General Public License v3.0
|
|
17
17
|
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* // Initialize with defaults
|
|
21
|
-
* const scrolling = new
|
|
21
|
+
* const scrolling = new Madj2kScrolling();
|
|
22
22
|
*
|
|
23
23
|
* @example
|
|
24
24
|
* // Initialize with custom config
|
|
25
25
|
* const scrolling = new Madj2kScrolling({
|
|
26
26
|
* anchorScrollingCollapsibleSelector: ['.collapse', '.custom-collapse'],
|
|
27
|
-
* anchorScrollingSelector: ['a[href^="#"]', '.btn-scroll']
|
|
27
|
+
* anchorScrollingSelector: ['a[href^="#"]', '.btn-scroll'],
|
|
28
28
|
* anchorScrollingOffsetSelector: '#siteheader',
|
|
29
|
-
*
|
|
29
|
+
* anchorScrollingDisableSelector: '.js-no-scroll',
|
|
30
30
|
* appearOnScrollSelector: ['.js-appear-on-scroll'],
|
|
31
31
|
* appearOnScrollTimeout: 500,
|
|
32
|
-
* appearOnScrollThreshold: 25
|
|
32
|
+
* appearOnScrollThreshold: 25,
|
|
33
|
+
* debug: true
|
|
33
34
|
* });
|
|
34
35
|
*
|
|
35
36
|
* @example
|
|
@@ -66,66 +67,70 @@ class Madj2kScrolling {
|
|
|
66
67
|
anchorScrollingSelector: ['a[href^="#"]'],
|
|
67
68
|
anchorScrollingOffsetSelector: '',
|
|
68
69
|
anchorScrollingScriptScrollTimeout: 800,
|
|
69
|
-
|
|
70
|
+
anchorScrollingDisableSelector: '.js-no-scroll',
|
|
70
71
|
anchorScrollingCollapsibleSelector: ['.collapse'],
|
|
71
72
|
anchorScrollingBehavior: 'smooth',
|
|
72
73
|
appearOnScrollSelector: ['.js-appear-on-scroll'],
|
|
73
74
|
appearOnScrollTimeout: 500,
|
|
74
|
-
appearOnScrollThreshold: 25
|
|
75
|
+
appearOnScrollThreshold: 25,
|
|
76
|
+
debug: false
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
constructor(config) {
|
|
78
80
|
this.config = { ...this.config, ...config };
|
|
79
|
-
|
|
81
|
+
|
|
82
|
+
this.lastScrollTop = window.scrollY;
|
|
83
|
+
this.lastContentHeight = document.documentElement.scrollHeight;
|
|
84
|
+
|
|
85
|
+
this._log('Initialized with config:', this.config);
|
|
80
86
|
|
|
81
87
|
this.initScrollClassesForBody();
|
|
82
88
|
this.initAnchorScrolling();
|
|
83
89
|
this.initAppearOnScroll();
|
|
84
90
|
}
|
|
85
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Adds scroll classes to body based on scroll direction
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
86
96
|
initScrollClassesForBody() {
|
|
87
|
-
const body = document.body;
|
|
88
|
-
let lastScrollTop = window.scrollY;
|
|
89
|
-
let lastContentHeight = document.documentElement.scrollHeight;
|
|
90
|
-
|
|
91
|
-
const setContentHeight = () => {
|
|
92
|
-
lastContentHeight = document.documentElement.scrollHeight;
|
|
93
|
-
body.setAttribute('data-last-content-height', lastContentHeight);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
97
|
const addScrollClasses = () => {
|
|
97
98
|
const scrollTop = window.scrollY;
|
|
98
99
|
const contentHeight = document.documentElement.scrollHeight;
|
|
99
100
|
|
|
100
|
-
if (
|
|
101
|
+
if (this.lastContentHeight !== contentHeight) {
|
|
102
|
+
this._log('Content height changed, skipping scroll class update');
|
|
103
|
+
this.lastContentHeight = contentHeight;
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
if (!body.classList.contains('block-scroll-classes')) {
|
|
107
|
+
if (!document.body.classList.contains('block-scroll-classes')) {
|
|
105
108
|
if (scrollTop > 0) {
|
|
106
|
-
body.classList.remove('scroll-up', 'scroll-down');
|
|
107
|
-
if (scrollTop > lastScrollTop) {
|
|
108
|
-
body.classList.add('scroll-down');
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
document.body.classList.remove('scroll-up', 'scroll-down');
|
|
110
|
+
if (scrollTop > this.lastScrollTop) {
|
|
111
|
+
document.body.classList.add('scroll-down');
|
|
112
|
+
this._log('Scroll direction: down');
|
|
113
|
+
} else if (scrollTop < this.lastScrollTop) {
|
|
114
|
+
document.body.classList.add('scroll-up');
|
|
115
|
+
this._log('Scroll direction: up');
|
|
111
116
|
}
|
|
112
117
|
} else {
|
|
113
|
-
body.classList.remove('scroll-down');
|
|
118
|
+
document.body.classList.remove('scroll-down');
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
lastScrollTop = scrollTop;
|
|
122
|
+
this.lastScrollTop = scrollTop;
|
|
119
123
|
};
|
|
120
124
|
|
|
121
|
-
setContentHeight();
|
|
122
|
-
|
|
123
125
|
window.addEventListener('scroll', () => {
|
|
124
|
-
setContentHeight();
|
|
125
126
|
addScrollClasses();
|
|
126
127
|
});
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Initializes anchor scrolling with optional offset
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
129
134
|
initAnchorScrolling() {
|
|
130
135
|
const offsetElement = document.querySelector(this.config.anchorScrollingOffsetSelector);
|
|
131
136
|
let scriptScrollTimer = null;
|
|
@@ -140,7 +145,6 @@ class Madj2kScrolling {
|
|
|
140
145
|
}
|
|
141
146
|
|
|
142
147
|
document.body.classList.add('block-scroll-classes');
|
|
143
|
-
|
|
144
148
|
if (scriptScrollTimer) {
|
|
145
149
|
clearTimeout(scriptScrollTimer);
|
|
146
150
|
}
|
|
@@ -153,6 +157,8 @@ class Madj2kScrolling {
|
|
|
153
157
|
top: scrollTo,
|
|
154
158
|
behavior: this.config.anchorScrollingBehavior
|
|
155
159
|
});
|
|
160
|
+
|
|
161
|
+
this._log('Anchor scroll to:', element);
|
|
156
162
|
}
|
|
157
163
|
};
|
|
158
164
|
|
|
@@ -168,7 +174,6 @@ class Madj2kScrolling {
|
|
|
168
174
|
event.preventDefault();
|
|
169
175
|
|
|
170
176
|
const anchorId = event.currentTarget.getAttribute('href');
|
|
171
|
-
|
|
172
177
|
if (anchorId && anchorId.startsWith('#')) {
|
|
173
178
|
const anchor = document.querySelector(anchorId);
|
|
174
179
|
if (anchor) scrollToElement(anchor);
|
|
@@ -177,13 +182,13 @@ class Madj2kScrolling {
|
|
|
177
182
|
|
|
178
183
|
const getAnchorSelector = () => {
|
|
179
184
|
return this.config.anchorScrollingSelector
|
|
180
|
-
.map(sel => `${sel}:not(.visually-hidden-focusable):not(
|
|
185
|
+
.map(sel => `${sel}:not(.visually-hidden-focusable):not(${this.config.anchorScrollingDisableSelector})`)
|
|
181
186
|
.join(', ');
|
|
182
187
|
};
|
|
183
188
|
|
|
184
189
|
const getCollapsibleSelector = () => {
|
|
185
190
|
return this.config.anchorScrollingCollapsibleSelector
|
|
186
|
-
.map(sel => `${sel}:not(
|
|
191
|
+
.map(sel => `${sel}:not(${this.config.anchorScrollingDisableSelector})`)
|
|
187
192
|
.join(', ');
|
|
188
193
|
};
|
|
189
194
|
|
|
@@ -195,6 +200,7 @@ class Madj2kScrolling {
|
|
|
195
200
|
document.querySelectorAll(getCollapsibleSelector())
|
|
196
201
|
.forEach(el => {
|
|
197
202
|
el.addEventListener('shown.bs.collapse', e => {
|
|
203
|
+
this._log('Collapse shown, scroll to:', e.target);
|
|
198
204
|
scrollToElement(e.target);
|
|
199
205
|
});
|
|
200
206
|
});
|
|
@@ -202,6 +208,10 @@ class Madj2kScrolling {
|
|
|
202
208
|
jumpToAnchorByUrl();
|
|
203
209
|
}
|
|
204
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Initializes appear-on-scroll animations
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
205
215
|
initAppearOnScroll() {
|
|
206
216
|
const initElement = (element) => {
|
|
207
217
|
const rect = element.getBoundingClientRect();
|
|
@@ -210,6 +220,7 @@ class Madj2kScrolling {
|
|
|
210
220
|
|
|
211
221
|
if (windowBottom > elementTop) {
|
|
212
222
|
element.setAttribute('data-appear-on-scroll', 0);
|
|
223
|
+
this._log('Appear on scroll (init):', element);
|
|
213
224
|
} else {
|
|
214
225
|
element.setAttribute('data-appear-on-scroll', 1);
|
|
215
226
|
}
|
|
@@ -220,8 +231,9 @@ class Madj2kScrolling {
|
|
|
220
231
|
const windowBottom = window.scrollY + window.innerHeight;
|
|
221
232
|
const elementTop = window.scrollY + rect.top;
|
|
222
233
|
|
|
223
|
-
if (windowBottom > elementTop) {
|
|
234
|
+
if (windowBottom > elementTop && element.getAttribute('data-appear-on-scroll') !== '0') {
|
|
224
235
|
element.setAttribute('data-appear-on-scroll', 0);
|
|
236
|
+
this._log('Appear on scroll (show):', element);
|
|
225
237
|
}
|
|
226
238
|
};
|
|
227
239
|
|
|
@@ -241,4 +253,14 @@ class Madj2kScrolling {
|
|
|
241
253
|
|
|
242
254
|
window.addEventListener('scroll', updateOnScroll);
|
|
243
255
|
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Debug logging helper
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
_log(...args) {
|
|
262
|
+
if (this.config.debug) {
|
|
263
|
+
console.log('[Madj2kScrolling]', ...args);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
244
266
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**!
|
|
2
2
|
* ToggledOverlay
|
|
3
3
|
*
|
|
4
|
+
* This class toggles the visibility of any target element referenced by the `aria-controls`
|
|
5
|
+
* attribute of a trigger element (button, link, etc.). It manages ARIA attributes for accessibility
|
|
6
|
+
* and allows overlays to be closed externally via a custom event.
|
|
7
|
+
*
|
|
4
8
|
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
5
9
|
* @copyright 2025 Steffen Kroggel
|
|
6
|
-
* @version 2.0.
|
|
10
|
+
* @version 2.0.1
|
|
7
11
|
* @license GNU General Public License v3.0
|
|
8
12
|
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
9
13
|
*
|
|
10
|
-
* This class toggles the visibility of any target element referenced by the `aria-controls`
|
|
11
|
-
* attribute of a trigger element (button, link, etc.). It manages ARIA attributes for accessibility
|
|
12
|
-
* and allows overlays to be closed externally via a custom event.
|
|
13
|
-
*
|
|
14
14
|
* @example
|
|
15
15
|
* Example HTML:
|
|
16
16
|
* <button class="js-toggled-overlay toggled-overlay-button"
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ResizeEnd Events (Vanilla JS)
|
|
3
|
-
**
|
|
4
|
-
* A lightweight helper class that triggers a debounced 'madj2k-resize-end' event
|
|
5
|
-
* when the user finishes resizing the browser window.
|
|
6
|
-
*
|
|
7
|
-
* It also manages a scrolling detection state (via body attribute) to avoid triggering
|
|
8
|
-
* resize-end during user scrolling, and respects active input fields (useful on mobile).
|
|
9
|
-
*
|
|
10
|
-
* @author Steffen Kroggel <developer@steffenkroggel.de>
|
|
11
|
-
* @copyright 2025 Steffen Kroggel
|
|
12
|
-
* @version 2.0.0
|
|
13
|
-
* @license GNU General Public License v3.0
|
|
14
|
-
* @see https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* // Initialize with defaults
|
|
18
|
-
* const resizeEnd = new Madj2kResizeEnd();
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* // Initialize with custom config
|
|
22
|
-
* const resizeEnd = new Madj2kResizeEnd({
|
|
23
|
-
* resizeEndTimeout: 300,
|
|
24
|
-
* scrollingEndTimeout: 600
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* // Listen to resize-end event
|
|
29
|
-
* document.addEventListener('madj2k-resize-end', () => {
|
|
30
|
-
* console.log('Resize-End fired');
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* // Example use case: reload masonry grid after resize ends
|
|
35
|
-
* document.addEventListener('madj2k-resize-end', () => {
|
|
36
|
-
* myMasonry.reloadItems();
|
|
37
|
-
* myMasonry.layout();
|
|
38
|
-
* });
|
|
39
|
-
*/
|
|
40
|
-
class Madj2kResizeEnd {
|
|
41
|
-
|
|
42
|
-
config = {
|
|
43
|
-
scrollingEndTimeout: 500,
|
|
44
|
-
resizeEndTimeout: 200,
|
|
45
|
-
scrollingDataAttr: 'data-resizeend-scrolling'
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
finalEventTimers = {};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Constructor
|
|
52
|
-
* @param {Object} config - Optional config overrides
|
|
53
|
-
*/
|
|
54
|
-
constructor(config = {}) {
|
|
55
|
-
this.config = { ...this.config, ...config };
|
|
56
|
-
this.initScrollingDetection();
|
|
57
|
-
this.initResizeEndEvent();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Init resizeEnd custom event
|
|
62
|
-
*/
|
|
63
|
-
initResizeEndEvent() {
|
|
64
|
-
window.addEventListener('resize', () => {
|
|
65
|
-
const body = document.body;
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
!body.hasAttribute(this.config.scrollingDataAttr) ||
|
|
69
|
-
body.getAttribute(this.config.scrollingDataAttr) === '0'
|
|
70
|
-
) {
|
|
71
|
-
this.waitForFinalEvent(() => {
|
|
72
|
-
// Skip if input is focused (e.g. keyboard open on mobile)
|
|
73
|
-
const active = document.activeElement;
|
|
74
|
-
if (!(active && active.tagName === 'INPUT')) {
|
|
75
|
-
const event = new CustomEvent('madj2k-resize-end');
|
|
76
|
-
document.dispatchEvent(event);
|
|
77
|
-
}
|
|
78
|
-
}, this.config.resizeEndTimeout, 'resize');
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Init scrolling detection
|
|
85
|
-
*/
|
|
86
|
-
initScrollingDetection() {
|
|
87
|
-
const handler = () => {
|
|
88
|
-
document.body.setAttribute(this.config.scrollingDataAttr, '1');
|
|
89
|
-
|
|
90
|
-
this.waitForFinalEvent(() => {
|
|
91
|
-
document.body.setAttribute(this.config.scrollingDataAttr, '0');
|
|
92
|
-
}, this.config.scrollingEndTimeout, 'scrolling');
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
window.addEventListener('scroll', handler, { passive: true });
|
|
96
|
-
window.addEventListener('touchmove', handler, { passive: true });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Debounced final event dispatcher
|
|
101
|
-
*/
|
|
102
|
-
waitForFinalEvent(callback, ms, uniqueId = "default") {
|
|
103
|
-
if (this.finalEventTimers[uniqueId]) {
|
|
104
|
-
clearTimeout(this.finalEventTimers[uniqueId]);
|
|
105
|
-
}
|
|
106
|
-
this.finalEventTimers[uniqueId] = setTimeout(callback, ms);
|
|
107
|
-
}
|
|
108
|
-
}
|