@brandocms/jupiter 3.55.0 → 4.0.0-beta.2
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/README.md +509 -54
- package/package.json +30 -18
- package/src/index.js +15 -10
- package/src/modules/Application/index.js +236 -158
- package/src/modules/Breakpoints/index.js +116 -36
- package/src/modules/Cookies/index.js +95 -64
- package/src/modules/CoverOverlay/index.js +21 -14
- package/src/modules/Dataloader/index.js +71 -24
- package/src/modules/Dataloader/url-sync.js +238 -0
- package/src/modules/Dom/index.js +24 -0
- package/src/modules/DoubleHeader/index.js +571 -0
- package/src/modules/Dropdown/index.js +108 -73
- package/src/modules/EqualHeightElements/index.js +8 -8
- package/src/modules/EqualHeightImages/index.js +15 -7
- package/src/modules/FixedHeader/index.js +116 -30
- package/src/modules/FooterReveal/index.js +5 -5
- package/src/modules/HeroSlider/index.js +231 -106
- package/src/modules/HeroVideo/index.js +72 -44
- package/src/modules/Lazyload/index.js +128 -80
- package/src/modules/Lightbox/index.js +101 -80
- package/src/modules/Links/index.js +77 -51
- package/src/modules/Looper/index.js +1737 -0
- package/src/modules/Marquee/index.js +106 -37
- package/src/modules/MobileMenu/index.js +105 -130
- package/src/modules/Moonwalk/index.js +479 -153
- package/src/modules/Parallax/index.js +280 -57
- package/src/modules/Popover/index.js +187 -17
- package/src/modules/Popup/index.js +172 -53
- package/src/modules/ScrollSpy/index.js +21 -0
- package/src/modules/StackedBoxes/index.js +8 -6
- package/src/modules/StickyHeader/index.js +394 -164
- package/src/modules/Toggler/index.js +207 -11
- package/src/modules/Typography/index.js +33 -20
- package/src/utils/motion-helpers.js +330 -0
- package/types/README.md +159 -0
- package/types/events/index.d.ts +20 -0
- package/types/index.d.ts +6 -0
- package/types/modules/Application/index.d.ts +168 -0
- package/types/modules/Breakpoints/index.d.ts +40 -0
- package/types/modules/Cookies/index.d.ts +81 -0
- package/types/modules/CoverOverlay/index.d.ts +6 -0
- package/types/modules/Dataloader/index.d.ts +38 -0
- package/types/modules/Dataloader/url-sync.d.ts +36 -0
- package/types/modules/Dom/index.d.ts +47 -0
- package/types/modules/DoubleHeader/index.d.ts +63 -0
- package/types/modules/Dropdown/index.d.ts +15 -0
- package/types/modules/EqualHeightElements/index.d.ts +8 -0
- package/types/modules/EqualHeightImages/index.d.ts +11 -0
- package/types/modules/FeatureTests/index.d.ts +27 -0
- package/types/modules/FixedHeader/index.d.ts +219 -0
- package/types/modules/Fontloader/index.d.ts +5 -0
- package/types/modules/FooterReveal/index.d.ts +5 -0
- package/types/modules/HeroSlider/index.d.ts +28 -0
- package/types/modules/HeroVideo/index.d.ts +83 -0
- package/types/modules/Lazyload/index.d.ts +80 -0
- package/types/modules/Lightbox/index.d.ts +123 -0
- package/types/modules/Links/index.d.ts +55 -0
- package/types/modules/Looper/index.d.ts +127 -0
- package/types/modules/Marquee/index.d.ts +23 -0
- package/types/modules/MobileMenu/index.d.ts +63 -0
- package/types/modules/Moonwalk/index.d.ts +322 -0
- package/types/modules/Parallax/index.d.ts +71 -0
- package/types/modules/Popover/index.d.ts +29 -0
- package/types/modules/Popup/index.d.ts +76 -0
- package/types/modules/ScrollSpy/index.d.ts +29 -0
- package/types/modules/StackedBoxes/index.d.ts +9 -0
- package/types/modules/StickyHeader/index.d.ts +220 -0
- package/types/modules/Toggler/index.d.ts +48 -0
- package/types/modules/Typography/index.d.ts +77 -0
- package/types/utils/dispatchElementEvent.d.ts +1 -0
- package/types/utils/imageIsLoaded.d.ts +1 -0
- package/types/utils/imagesAreLoaded.d.ts +1 -0
- package/types/utils/loadScript.d.ts +2 -0
- package/types/utils/prefersReducedMotion.d.ts +4 -0
- package/types/utils/rafCallback.d.ts +2 -0
- package/types/utils/zoom.d.ts +4 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A header that
|
|
2
|
+
* A header that uses position: sticky. Hides when scrolling down and is revealed on scrolling up.
|
|
3
|
+
* Unlike FixedHeader, the sticky header stays in document flow - when hidden via transform,
|
|
4
|
+
* its space is still reserved.
|
|
3
5
|
*
|
|
4
6
|
* You can pass different configs for different sections:
|
|
5
7
|
*
|
|
@@ -23,97 +25,188 @@
|
|
|
23
25
|
*
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
|
-
import {
|
|
28
|
+
import { animate, stagger } from 'motion'
|
|
27
29
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
28
30
|
import * as Events from '../../events'
|
|
31
|
+
import Dom from '../Dom'
|
|
32
|
+
import { set } from '../../utils/motion-helpers'
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} StickyHeaderEvents
|
|
36
|
+
* @property {Function} [onPin] - Called when header is pinned
|
|
37
|
+
* @property {Function} [onUnpin] - Called when header is unpinned
|
|
38
|
+
* @property {Function} [onAltBg] - Called when alternate background is applied
|
|
39
|
+
* @property {Function} [onNotAltBg] - Called when regular background is applied
|
|
40
|
+
* @property {Function} [onSmall] - Called when header becomes small
|
|
41
|
+
* @property {Function} [onNotSmall] - Called when header becomes normal size
|
|
42
|
+
* @property {Function} [onTop] - Called when page is at the top
|
|
43
|
+
* @property {Function} [onNotTop] - Called when page is not at the top
|
|
44
|
+
* @property {Function} [onBottom] - Called when page is at the bottom
|
|
45
|
+
* @property {Function} [onNotBottom] - Called when page is not at the bottom
|
|
46
|
+
* @property {Function} [onMobileMenuOpen] - Called when mobile menu opens
|
|
47
|
+
* @property {Function} [onMobileMenuClose] - Called when mobile menu closes
|
|
48
|
+
* @property {Function} [onIntersect] - Called when header intersects with an element
|
|
49
|
+
* @property {Function} [onOutline] - Called when user tabs (outline mode)
|
|
50
|
+
*/
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} StickyHeaderSectionOptions
|
|
54
|
+
* @property {boolean} [unPinOnResize=true] - Whether to unpin header on window resize
|
|
55
|
+
* @property {Window|HTMLElement} [canvas=window] - Scrolling element
|
|
56
|
+
* @property {string|null} [intersects=null] - Selector for elements to check intersection with
|
|
57
|
+
* @property {Function} [beforeEnter] - Called before header enters
|
|
58
|
+
* @property {Function} [enter] - Called when header enters
|
|
59
|
+
* @property {number} [enterDelay=0] - Delay before enter animation
|
|
60
|
+
* @property {number} [tolerance=3] - Scroll tolerance before triggering hide/show
|
|
61
|
+
* @property {number|string|Function} [offset=0] - Offset from top before triggering hide
|
|
62
|
+
* @property {number|string|Function} [offsetSmall=50] - Offset from top before shrinking header
|
|
63
|
+
* @property {number|string|Function} [offsetBg=200] - Offset from top before changing background color
|
|
64
|
+
* @property {string|null} [regBgColor=null] - Regular background color
|
|
65
|
+
* @property {string|null} [altBgColor=null] - Alternate background color
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {Object} StickyHeaderOptions
|
|
70
|
+
* @property {string|HTMLElement} [el='header[data-nav]'] - Header element or selector
|
|
71
|
+
* @property {string} [on=Events.APPLICATION_REVEALED] - Event to initialize on
|
|
72
|
+
* @property {boolean} [unpinOnForcedScrollStart=true] - Whether to unpin on forced scroll start
|
|
73
|
+
* @property {boolean} [pinOnForcedScrollEnd=true] - Whether to pin on forced scroll end
|
|
74
|
+
* @property {boolean} [ignoreForcedScroll=false] - Whether to ignore forced scroll events
|
|
75
|
+
* @property {boolean} [rafScroll=true] - Whether to use requestAnimationFrame for scrolling
|
|
76
|
+
* @property {StickyHeaderSectionOptions} [default] - Default options for all sections
|
|
77
|
+
* @property {Object.<string, StickyHeaderSectionOptions>} [sections] - Section-specific options
|
|
78
|
+
*/
|
|
45
79
|
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
/** @type {StickyHeaderEvents} */
|
|
81
|
+
const DEFAULT_EVENTS = {
|
|
82
|
+
onPin: (h) => {
|
|
83
|
+
animate(h.el, {
|
|
84
|
+
yPercent: '0'
|
|
85
|
+
}, {
|
|
48
86
|
duration: 0.35,
|
|
49
|
-
|
|
50
|
-
ease: 'sine.out',
|
|
51
|
-
autoRound: true
|
|
87
|
+
ease: 'easeOut'
|
|
52
88
|
})
|
|
53
89
|
},
|
|
54
90
|
|
|
55
|
-
onUnpin: h => {
|
|
91
|
+
onUnpin: (h) => {
|
|
56
92
|
h._hiding = true
|
|
57
|
-
|
|
93
|
+
animate(h.el, {
|
|
94
|
+
yPercent: '-100'
|
|
95
|
+
}, {
|
|
58
96
|
duration: 0.25,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
onComplete: () => {
|
|
63
|
-
h._hiding = false
|
|
64
|
-
}
|
|
97
|
+
ease: 'easeIn'
|
|
98
|
+
}).finished.then(() => {
|
|
99
|
+
h._hiding = false
|
|
65
100
|
})
|
|
66
101
|
},
|
|
67
|
-
|
|
102
|
+
|
|
103
|
+
onAltBg: (h) => {
|
|
104
|
+
if (h.opts.altBgColor) {
|
|
105
|
+
animate(h.el, {
|
|
106
|
+
backgroundColor: h.opts.altBgColor
|
|
107
|
+
}, {
|
|
108
|
+
duration: 0.2
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
onNotAltBg: (h) => {
|
|
114
|
+
if (h.opts.regBgColor) {
|
|
115
|
+
animate(h.el, {
|
|
116
|
+
backgroundColor: h.opts.regBgColor
|
|
117
|
+
}, {
|
|
118
|
+
duration: 0.4
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// eslint-disable-next-line no-unused-vars
|
|
124
|
+
onSmall: (h) => {},
|
|
125
|
+
// eslint-disable-next-line no-unused-vars
|
|
126
|
+
onNotSmall: (h) => {},
|
|
127
|
+
// eslint-disable-next-line no-unused-vars
|
|
128
|
+
onTop: (h) => {},
|
|
129
|
+
// eslint-disable-next-line no-unused-vars
|
|
130
|
+
onNotTop: (h) => {},
|
|
131
|
+
// eslint-disable-next-line no-unused-vars
|
|
132
|
+
onBottom: (h) => {},
|
|
133
|
+
// eslint-disable-next-line no-unused-vars
|
|
134
|
+
onNotBottom: (h) => {},
|
|
135
|
+
// eslint-disable-next-line no-unused-vars
|
|
136
|
+
onMobileMenuOpen: (h) => {},
|
|
137
|
+
// eslint-disable-next-line no-unused-vars
|
|
138
|
+
onMobileMenuClose: (h) => {},
|
|
139
|
+
// eslint-disable-next-line no-unused-vars
|
|
140
|
+
onIntersect: (h) => {},
|
|
141
|
+
onOutline: (h) => {
|
|
142
|
+
h.preventUnpin = true
|
|
143
|
+
h.pin()
|
|
144
|
+
},
|
|
68
145
|
}
|
|
69
146
|
|
|
147
|
+
/** @type {StickyHeaderOptions} */
|
|
70
148
|
const DEFAULT_OPTIONS = {
|
|
71
149
|
el: 'header[data-nav]',
|
|
72
150
|
on: Events.APPLICATION_REVEALED,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
unpinOnForcedScrollStart: true,
|
|
152
|
+
pinOnForcedScrollEnd: true,
|
|
153
|
+
ignoreForcedScroll: false,
|
|
154
|
+
rafScroll: true,
|
|
76
155
|
|
|
77
156
|
default: {
|
|
78
|
-
|
|
157
|
+
unPinOnResize: true,
|
|
79
158
|
canvas: window,
|
|
80
|
-
|
|
81
|
-
|
|
159
|
+
intersects: null,
|
|
160
|
+
beforeEnter: (h) => {
|
|
161
|
+
set(h.el, { yPercent: -100 })
|
|
162
|
+
set(h.lis, { opacity: 0 })
|
|
82
163
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
|
|
165
|
+
enter: (h) => {
|
|
166
|
+
// Header slides down
|
|
167
|
+
animate(h.el, {
|
|
168
|
+
yPercent: 0
|
|
169
|
+
}, {
|
|
170
|
+
duration: 1,
|
|
171
|
+
delay: h.opts.enterDelay,
|
|
172
|
+
ease: 'easeOut'
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Menu items fade in with stagger (starts at same time as header: '-=1' means 1s overlap)
|
|
176
|
+
animate(h.lis, {
|
|
177
|
+
opacity: 1
|
|
178
|
+
}, {
|
|
179
|
+
duration: 0.8,
|
|
180
|
+
delay: stagger(0.1, { startDelay: h.opts.enterDelay }),
|
|
181
|
+
ease: 'easeIn'
|
|
182
|
+
})
|
|
95
183
|
},
|
|
96
|
-
|
|
184
|
+
|
|
185
|
+
enterDelay: 0,
|
|
97
186
|
tolerance: 3,
|
|
98
187
|
offset: 0, // how far from the top before we trigger hide
|
|
99
188
|
offsetSmall: 50, // how far from the top before we trigger the shrinked padding,
|
|
100
189
|
offsetBg: 200, // how far down before changing backgroundcolor
|
|
101
|
-
|
|
102
|
-
|
|
190
|
+
regBgColor: null,
|
|
191
|
+
altBgColor: null,
|
|
192
|
+
...DEFAULT_EVENTS,
|
|
193
|
+
},
|
|
103
194
|
}
|
|
104
195
|
|
|
196
|
+
/**
|
|
197
|
+
* StickyHeader component for sticky navigation headers with scroll behaviors.
|
|
198
|
+
* Uses position: sticky instead of position: fixed, keeping the header in document flow.
|
|
199
|
+
*/
|
|
105
200
|
export default class StickyHeader {
|
|
201
|
+
/**
|
|
202
|
+
* Create a new StickyHeader instance
|
|
203
|
+
* @param {Object} app - Application instance
|
|
204
|
+
* @param {StickyHeaderOptions} [opts={}] - StickyHeader options
|
|
205
|
+
*/
|
|
106
206
|
constructor(app, opts = {}) {
|
|
107
207
|
this.app = app
|
|
108
208
|
this.mainOpts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
109
209
|
|
|
110
|
-
if (this.mainOpts.pinOnOutline) {
|
|
111
|
-
window.addEventListener(Events.APPLICATION_OUTLINE, () => {
|
|
112
|
-
this.preventUnpin = true
|
|
113
|
-
this.pin()
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
210
|
if (typeof this.mainOpts.el === 'string') {
|
|
118
211
|
this.el = document.querySelector(this.mainOpts.el)
|
|
119
212
|
} else {
|
|
@@ -125,34 +218,36 @@ export default class StickyHeader {
|
|
|
125
218
|
}
|
|
126
219
|
|
|
127
220
|
const section = document.body.getAttribute('data-script')
|
|
128
|
-
this.opts = this._getOptionsForSection(section, opts)
|
|
129
|
-
|
|
130
|
-
this.auxEl = this.opts.onClone(this)
|
|
131
|
-
this.auxEl.setAttribute('data-header-pinned', '')
|
|
132
|
-
this.auxEl.setAttribute('data-auxiliary-nav', '')
|
|
133
|
-
this.auxEl.removeAttribute('data-nav')
|
|
134
|
-
|
|
135
|
-
document.body.appendChild(this.auxEl)
|
|
136
|
-
|
|
137
|
-
this.small()
|
|
138
|
-
this.unpin()
|
|
139
221
|
|
|
222
|
+
this.opts = this._getOptionsForSection(section, opts)
|
|
140
223
|
this.lis = this.el.querySelectorAll('li')
|
|
224
|
+
|
|
141
225
|
this.preventPin = false
|
|
142
226
|
this.preventUnpin = false
|
|
143
|
-
this._isResizing = false
|
|
144
227
|
this._firstLoad = true
|
|
145
228
|
this._pinned = true
|
|
146
229
|
this._top = false
|
|
147
230
|
this._bottom = false
|
|
148
231
|
this._small = false
|
|
232
|
+
this._altBg = false
|
|
233
|
+
this._isResizing = false
|
|
149
234
|
this._hiding = false // if we're in the process of hiding the bar
|
|
150
235
|
this.lastKnownScrollY = 0
|
|
236
|
+
this.lastKnownScrollHeight = 0
|
|
237
|
+
this.currentScrollHeight = 0
|
|
151
238
|
this.currentScrollY = 0
|
|
152
239
|
this.mobileMenuOpen = false
|
|
153
240
|
this.timer = null
|
|
154
241
|
this.resetResizeTimer = null
|
|
155
|
-
this.
|
|
242
|
+
this.scrollSettleTimeout = null
|
|
243
|
+
|
|
244
|
+
if (this.opts.intersects) {
|
|
245
|
+
this.intersectingElements = Dom.all('[data-intersect]')
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
window.addEventListener(Events.APPLICATION_OUTLINE, () => {
|
|
249
|
+
this.opts.onOutline(this)
|
|
250
|
+
})
|
|
156
251
|
|
|
157
252
|
this.initialize()
|
|
158
253
|
}
|
|
@@ -160,74 +255,159 @@ export default class StickyHeader {
|
|
|
160
255
|
initialize() {
|
|
161
256
|
// bind to canvas scroll
|
|
162
257
|
this.lastKnownScrollY = this.getScrollY()
|
|
258
|
+
this.lastKnownScrollHeight = document.body.scrollHeight
|
|
163
259
|
this.currentScrollY = this.lastKnownScrollY
|
|
260
|
+
this.currentScrollHeight = this.lastKnownScrollHeight
|
|
261
|
+
this.pageIsScrolledOnReady = false
|
|
164
262
|
|
|
165
263
|
if (typeof this.opts.offsetBg === 'string') {
|
|
166
264
|
// get offset of element, with height of header subtracted
|
|
167
|
-
const
|
|
168
|
-
this.opts.offsetBg =
|
|
265
|
+
const offsetBgElm = document.querySelector(this.opts.offsetBg)
|
|
266
|
+
this.opts.offsetBg = offsetBgElm.offsetTop
|
|
267
|
+
} else if (typeof this.opts.offsetBg === 'function') {
|
|
268
|
+
this.opts.offsetBg = this.opts.offsetBg(this) - 1
|
|
169
269
|
}
|
|
170
270
|
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
271
|
+
if (typeof this.opts.offset === 'string') {
|
|
272
|
+
// get offset of element, with height of header subtracted
|
|
273
|
+
const offsetElm = document.querySelector(this.opts.offset)
|
|
274
|
+
this.opts.offset = offsetElm.offsetTop - 1
|
|
275
|
+
} else if (typeof this.opts.offset === 'function') {
|
|
276
|
+
this.opts.offset = this.opts.offset(this) - 1
|
|
277
|
+
}
|
|
175
278
|
|
|
176
|
-
if (this.opts.
|
|
177
|
-
|
|
279
|
+
if (typeof this.opts.offsetSmall === 'string') {
|
|
280
|
+
// get offsetSmall of element, with height of header subtracted
|
|
281
|
+
const offsetSmallElm = document.querySelector(this.opts.offsetSmall)
|
|
282
|
+
this.opts.offsetSmall = offsetSmallElm.offsetTop - 1
|
|
283
|
+
} else if (typeof this.opts.offsetSmall === 'function') {
|
|
284
|
+
this.opts.offsetSmall = this.opts.offsetSmall(this) - 1
|
|
178
285
|
}
|
|
179
286
|
|
|
180
|
-
this.
|
|
181
|
-
|
|
287
|
+
if (this.mainOpts.unpinOnForcedScrollStart) {
|
|
288
|
+
window.addEventListener(
|
|
289
|
+
Events.APPLICATION_FORCED_SCROLL_START,
|
|
290
|
+
this.unpin.bind(this),
|
|
291
|
+
false
|
|
292
|
+
)
|
|
293
|
+
}
|
|
182
294
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
295
|
+
if (this.mainOpts.pinOnForcedScrollEnd) {
|
|
296
|
+
window.addEventListener(
|
|
297
|
+
Events.APPLICATION_FORCED_SCROLL_END,
|
|
298
|
+
this.pin.bind(this),
|
|
299
|
+
false
|
|
300
|
+
)
|
|
301
|
+
}
|
|
186
302
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.firstReveal = false
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
this._navVisible = true
|
|
195
|
-
} else {
|
|
196
|
-
if (this._navVisible === true) {
|
|
197
|
-
this.opts.onMainInvisible(this)
|
|
198
|
-
}
|
|
199
|
-
this._navVisible = false
|
|
303
|
+
this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
|
|
304
|
+
let SCROLL_EVENT = Events.APPLICATION_SCROLL
|
|
305
|
+
if (!this.mainOpts.rafScroll) {
|
|
306
|
+
SCROLL_EVENT = 'scroll'
|
|
200
307
|
}
|
|
201
|
-
})
|
|
202
308
|
|
|
203
|
-
|
|
309
|
+
window.addEventListener(SCROLL_EVENT, this.redraw.bind(this), {
|
|
310
|
+
capture: false,
|
|
311
|
+
passive: true,
|
|
312
|
+
})
|
|
204
313
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.
|
|
209
|
-
this.
|
|
314
|
+
// Add debounced scroll listener for accurate top/bottom detection after scroll settles
|
|
315
|
+
// RAF-throttled events can lag behind actual scroll position during fast scrolls
|
|
316
|
+
window.addEventListener('scroll', () => {
|
|
317
|
+
clearTimeout(this.scrollSettleTimeout)
|
|
318
|
+
this.scrollSettleTimeout = setTimeout(() => {
|
|
319
|
+
// Get real-time scroll position after scroll has settled
|
|
320
|
+
const actualScrollY = this.opts.canvas === window || this.opts.canvas === document.body
|
|
321
|
+
? window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
|
|
322
|
+
: this.opts.canvas.scrollTop
|
|
323
|
+
|
|
324
|
+
// Update current scroll and force accurate boundary checks
|
|
325
|
+
this.currentScrollY = actualScrollY
|
|
326
|
+
this.checkTop(true)
|
|
327
|
+
this.checkBot(true)
|
|
328
|
+
}, 100)
|
|
329
|
+
}, {
|
|
330
|
+
capture: false,
|
|
331
|
+
passive: true,
|
|
210
332
|
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
this.app.registerCallback(
|
|
336
|
+
Events.APPLICATION_READY,
|
|
337
|
+
this.unpinIfScrolled.bind(this)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
this.preflight()
|
|
341
|
+
|
|
342
|
+
window.addEventListener(this.mainOpts.on, this.enter.bind(this))
|
|
343
|
+
|
|
344
|
+
this._bindMobileMenuListeners()
|
|
345
|
+
|
|
346
|
+
// DON'T unpin on iOS since this will unpin when bottom menu bar appears on scrolling upwards!
|
|
347
|
+
if (this.opts.unPinOnResize && !this.app.featureTests.results.ios) {
|
|
211
348
|
window.addEventListener(
|
|
212
|
-
Events.
|
|
213
|
-
()
|
|
214
|
-
this.preventPin = false
|
|
215
|
-
this.pin()
|
|
216
|
-
this.preventUnpin = false
|
|
217
|
-
},
|
|
349
|
+
Events.APPLICATION_RESIZE,
|
|
350
|
+
this.setResizeTimer.bind(this),
|
|
218
351
|
false
|
|
219
352
|
)
|
|
220
353
|
}
|
|
354
|
+
|
|
355
|
+
this.opts.beforeEnter(this)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
preflight() {
|
|
359
|
+
if (!this.opts.enter) {
|
|
360
|
+
this.checkSize(true)
|
|
361
|
+
this.checkBg(true)
|
|
362
|
+
this.checkTop(true)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.app.registerCallback(Events.APPLICATION_REVEALED, () => {
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
this.el.setAttribute('data-header-transitions', '')
|
|
368
|
+
}, 350)
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
lock() {
|
|
373
|
+
this.preventPin = true
|
|
374
|
+
this.preventUnpin = true
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
unlock() {
|
|
378
|
+
this.preventPin = false
|
|
379
|
+
this.preventUnpin = false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
isScrolled() {
|
|
383
|
+
return (
|
|
384
|
+
(window.pageYOffset || document.documentElement.scrollTop) -
|
|
385
|
+
(document.documentElement.clientTop || 0) >
|
|
386
|
+
0
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
unpinIfScrolled() {
|
|
391
|
+
if (this.isScrolled()) {
|
|
392
|
+
// page is scrolled on ready -- ensure we unpin
|
|
393
|
+
this.pageIsScrolledOnReady = true
|
|
394
|
+
this.unpin()
|
|
395
|
+
}
|
|
221
396
|
}
|
|
222
397
|
|
|
223
|
-
|
|
224
|
-
this.
|
|
398
|
+
enter() {
|
|
399
|
+
if (this.opts.enter) {
|
|
400
|
+
this.checkSize(true)
|
|
401
|
+
this.checkBg(true)
|
|
402
|
+
this.checkTop(true)
|
|
403
|
+
this.opts.enter(this)
|
|
404
|
+
}
|
|
225
405
|
}
|
|
226
406
|
|
|
227
407
|
setResizeTimer() {
|
|
228
408
|
this._isResizing = true
|
|
229
409
|
if (this._pinned) {
|
|
230
|
-
// unpin if resizing to prevent visual clutter
|
|
410
|
+
// unpin if resizing to prevent visual clutter.
|
|
231
411
|
this.unpin()
|
|
232
412
|
}
|
|
233
413
|
|
|
@@ -241,26 +421,8 @@ export default class StickyHeader {
|
|
|
241
421
|
}, 500)
|
|
242
422
|
}
|
|
243
423
|
|
|
244
|
-
_hideAlt() {
|
|
245
|
-
this.unpin()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
_showAlt() {
|
|
249
|
-
this.pin()
|
|
250
|
-
}
|
|
251
|
-
|
|
252
424
|
update() {
|
|
253
|
-
this.redraw(
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
lock() {
|
|
257
|
-
this.preventPin = true
|
|
258
|
-
this.preventUnpin = true
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
unlock() {
|
|
262
|
-
this.preventPin = false
|
|
263
|
-
this.preventUnpin = false
|
|
425
|
+
this.redraw()
|
|
264
426
|
}
|
|
265
427
|
|
|
266
428
|
checkSize(force) {
|
|
@@ -277,6 +439,20 @@ export default class StickyHeader {
|
|
|
277
439
|
}
|
|
278
440
|
}
|
|
279
441
|
|
|
442
|
+
checkBg(force) {
|
|
443
|
+
if (this.currentScrollY > this.opts.offsetBg) {
|
|
444
|
+
if (force) {
|
|
445
|
+
this.altBg()
|
|
446
|
+
} else if (!this._altBg && !this._hiding) {
|
|
447
|
+
this.altBg()
|
|
448
|
+
}
|
|
449
|
+
} else if (force) {
|
|
450
|
+
this.notAltBg()
|
|
451
|
+
} else if (this._altBg) {
|
|
452
|
+
this.notAltBg()
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
280
456
|
checkTop(force) {
|
|
281
457
|
if (this.currentScrollY <= this.opts.offset) {
|
|
282
458
|
if (force) {
|
|
@@ -292,7 +468,10 @@ export default class StickyHeader {
|
|
|
292
468
|
}
|
|
293
469
|
|
|
294
470
|
checkBot(force) {
|
|
295
|
-
if (
|
|
471
|
+
if (
|
|
472
|
+
this.currentScrollY + this.getViewportHeight() >=
|
|
473
|
+
this.getScrollerHeight()
|
|
474
|
+
) {
|
|
296
475
|
if (force) {
|
|
297
476
|
this.bottom()
|
|
298
477
|
} else if (!this._bottom) {
|
|
@@ -306,29 +485,27 @@ export default class StickyHeader {
|
|
|
306
485
|
}
|
|
307
486
|
|
|
308
487
|
checkPin(force, toleranceExceeded) {
|
|
309
|
-
if (this._navVisible) {
|
|
310
|
-
if (this._pinned) {
|
|
311
|
-
this.unpin()
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
488
|
if (this.shouldUnpin(toleranceExceeded)) {
|
|
317
489
|
if (this.mobileMenuOpen) {
|
|
318
490
|
return
|
|
319
491
|
}
|
|
320
|
-
if (
|
|
492
|
+
if (force) {
|
|
493
|
+
this.unpin()
|
|
494
|
+
} else if (this._pinned) {
|
|
321
495
|
this.unpin()
|
|
322
496
|
}
|
|
323
497
|
} else if (this.shouldPin(toleranceExceeded)) {
|
|
324
|
-
if (
|
|
498
|
+
if (force) {
|
|
499
|
+
this.pin()
|
|
500
|
+
} else if (!this._pinned) {
|
|
325
501
|
this.pin()
|
|
326
502
|
}
|
|
327
503
|
}
|
|
328
504
|
}
|
|
329
505
|
|
|
330
|
-
redraw(
|
|
506
|
+
redraw() {
|
|
331
507
|
this.currentScrollY = this.getScrollY()
|
|
508
|
+
this.currentScrollHeight = document.body.scrollHeight
|
|
332
509
|
const toleranceExceeded = this.toleranceExceeded()
|
|
333
510
|
|
|
334
511
|
if (this.isOutOfBounds()) {
|
|
@@ -336,8 +513,31 @@ export default class StickyHeader {
|
|
|
336
513
|
return
|
|
337
514
|
}
|
|
338
515
|
|
|
339
|
-
|
|
516
|
+
/* content-visibility: auto may CHANGE the scrollheight of the document
|
|
517
|
+
as we roll down/up. Try to avoid false positives here */
|
|
518
|
+
if (
|
|
519
|
+
this.currentScrollHeight !== this.lastKnownScrollHeight &&
|
|
520
|
+
!this._firstLoad
|
|
521
|
+
) {
|
|
522
|
+
this.lastKnownScrollY = this.currentScrollY
|
|
523
|
+
this.lastKnownScrollHeight = this.currentScrollHeight
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
this.checkSize(false)
|
|
528
|
+
this.checkBg(false)
|
|
529
|
+
this.checkTop(false)
|
|
530
|
+
this.checkBot(false)
|
|
531
|
+
|
|
532
|
+
if (this.mainOpts.ignoreForcedScroll && this.app.state.forcedScroll) {
|
|
533
|
+
// ignore forced scroll
|
|
534
|
+
} else {
|
|
535
|
+
this.checkPin(false, toleranceExceeded)
|
|
536
|
+
}
|
|
537
|
+
|
|
340
538
|
this.lastKnownScrollY = this.currentScrollY
|
|
539
|
+
this.lastKnownScrollHeight = this.currentScrollHeight
|
|
540
|
+
|
|
341
541
|
this._firstLoad = false
|
|
342
542
|
}
|
|
343
543
|
|
|
@@ -370,38 +570,54 @@ export default class StickyHeader {
|
|
|
370
570
|
}
|
|
371
571
|
|
|
372
572
|
unpin() {
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
this.opts.onUnpin(this)
|
|
573
|
+
if (this.preventUnpin) {
|
|
574
|
+
return
|
|
376
575
|
}
|
|
576
|
+
this._pinned = false
|
|
577
|
+
this.el.setAttribute('data-header-unpinned', '')
|
|
578
|
+
this.el.removeAttribute('data-header-pinned')
|
|
579
|
+
this.opts.onUnpin(this)
|
|
377
580
|
}
|
|
378
581
|
|
|
379
582
|
pin() {
|
|
380
|
-
if (
|
|
381
|
-
|
|
382
|
-
this.opts.onSmall(this)
|
|
383
|
-
this.opts.onPin(this)
|
|
583
|
+
if (this.preventPin) {
|
|
584
|
+
return
|
|
384
585
|
}
|
|
586
|
+
this._pinned = true
|
|
587
|
+
this.el.setAttribute('data-header-pinned', '')
|
|
588
|
+
this.el.removeAttribute('data-header-unpinned')
|
|
589
|
+
this.opts.onPin(this)
|
|
385
590
|
}
|
|
386
591
|
|
|
387
592
|
notSmall() {
|
|
388
593
|
this._small = false
|
|
389
|
-
this.
|
|
390
|
-
this.
|
|
594
|
+
this.el.setAttribute('data-header-big', '')
|
|
595
|
+
this.el.removeAttribute('data-header-small')
|
|
391
596
|
this.opts.onNotSmall(this)
|
|
392
597
|
}
|
|
393
598
|
|
|
394
599
|
small() {
|
|
395
600
|
this._small = true
|
|
396
|
-
this.
|
|
397
|
-
this.
|
|
601
|
+
this.el.setAttribute('data-header-small', '')
|
|
602
|
+
this.el.removeAttribute('data-header-big')
|
|
398
603
|
this.opts.onSmall(this)
|
|
399
604
|
}
|
|
400
605
|
|
|
606
|
+
notAltBg() {
|
|
607
|
+
this._altBg = false
|
|
608
|
+
this.el.setAttribute('data-header-reg-bg', '')
|
|
609
|
+
this.el.removeAttribute('data-header-alt-bg')
|
|
610
|
+
this.opts.onNotAltBg(this)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
altBg() {
|
|
614
|
+
this._altBg = true
|
|
615
|
+
this.el.setAttribute('data-header-alt-bg', '')
|
|
616
|
+
this.el.removeAttribute('data-header-reg-bg')
|
|
617
|
+
this.opts.onAltBg(this)
|
|
618
|
+
}
|
|
619
|
+
|
|
401
620
|
shouldUnpin(toleranceExceeded) {
|
|
402
|
-
if (this._navVisible) {
|
|
403
|
-
return true
|
|
404
|
-
}
|
|
405
621
|
const scrollingDown = this.currentScrollY > this.lastKnownScrollY
|
|
406
622
|
const pastOffset = this.currentScrollY >= this.opts.offset
|
|
407
623
|
|
|
@@ -412,15 +628,18 @@ export default class StickyHeader {
|
|
|
412
628
|
if (this._isResizing) {
|
|
413
629
|
return false
|
|
414
630
|
}
|
|
631
|
+
|
|
415
632
|
const scrollingUp = this.currentScrollY < this.lastKnownScrollY
|
|
416
633
|
const pastOffset = this.currentScrollY <= this.opts.offset
|
|
634
|
+
|
|
417
635
|
return (scrollingUp && toleranceExceeded) || pastOffset
|
|
418
636
|
}
|
|
419
637
|
|
|
420
638
|
isOutOfBounds() {
|
|
421
639
|
const pastTop = this.currentScrollY < 0
|
|
422
640
|
const pastBottom =
|
|
423
|
-
this.currentScrollY + this.getScrollerPhysicalHeight() >
|
|
641
|
+
this.currentScrollY + this.getScrollerPhysicalHeight() >
|
|
642
|
+
this.getScrollerHeight()
|
|
424
643
|
|
|
425
644
|
return pastTop || pastBottom
|
|
426
645
|
}
|
|
@@ -453,7 +672,9 @@ export default class StickyHeader {
|
|
|
453
672
|
|
|
454
673
|
getViewportHeight() {
|
|
455
674
|
return (
|
|
456
|
-
window.innerHeight ||
|
|
675
|
+
window.innerHeight ||
|
|
676
|
+
document.documentElement.clientHeight ||
|
|
677
|
+
document.body.clientHeight
|
|
457
678
|
)
|
|
458
679
|
}
|
|
459
680
|
|
|
@@ -472,11 +693,18 @@ export default class StickyHeader {
|
|
|
472
693
|
if (this.opts.canvas.scrollTop !== undefined) {
|
|
473
694
|
return this.opts.canvas.scrollTop
|
|
474
695
|
}
|
|
475
|
-
return (
|
|
696
|
+
return (
|
|
697
|
+
document.documentElement ||
|
|
698
|
+
document.body.parentNode ||
|
|
699
|
+
document.body
|
|
700
|
+
).scrollTop
|
|
476
701
|
}
|
|
477
702
|
|
|
478
703
|
toleranceExceeded() {
|
|
479
|
-
return
|
|
704
|
+
return (
|
|
705
|
+
Math.abs(this.currentScrollY - this.lastKnownScrollY) >=
|
|
706
|
+
this.opts.tolerance
|
|
707
|
+
)
|
|
480
708
|
}
|
|
481
709
|
|
|
482
710
|
_getOptionsForSection(section, opts) {
|
|
@@ -496,20 +724,22 @@ export default class StickyHeader {
|
|
|
496
724
|
|
|
497
725
|
_bindMobileMenuListeners() {
|
|
498
726
|
window.addEventListener(
|
|
499
|
-
|
|
727
|
+
'APPLICATION:MOBILE_MENU:OPEN',
|
|
500
728
|
this._onMobileMenuOpen.bind(this)
|
|
501
729
|
)
|
|
502
730
|
window.addEventListener(
|
|
503
|
-
|
|
731
|
+
'APPLICATION:MOBILE_MENU:CLOSED',
|
|
504
732
|
this._onMobileMenuClose.bind(this)
|
|
505
733
|
)
|
|
506
734
|
}
|
|
507
735
|
|
|
508
736
|
_onMobileMenuOpen() {
|
|
737
|
+
this.opts.onMobileMenuOpen(this)
|
|
509
738
|
this.mobileMenuOpen = true
|
|
510
739
|
}
|
|
511
740
|
|
|
512
741
|
_onMobileMenuClose() {
|
|
742
|
+
this.opts.onMobileMenuClose(this)
|
|
513
743
|
this.mobileMenuOpen = false
|
|
514
744
|
}
|
|
515
745
|
}
|