@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
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import { CSSPlugin } from 'gsap/CSSPlugin'
|
|
12
|
+
import { animate } from 'motion'
|
|
14
13
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
15
14
|
import prefersReducedMotion from '../../utils/prefersReducedMotion'
|
|
16
15
|
import * as Events from '../../events'
|
|
17
16
|
import imageIsLoaded from '../../utils/imageIsLoaded'
|
|
18
|
-
|
|
19
|
-
gsap.registerPlugin(CSSPlugin)
|
|
17
|
+
import { set, delayedCall } from '../../utils/motion-helpers'
|
|
20
18
|
|
|
21
19
|
const DEFAULT_OPTIONS = {
|
|
22
20
|
el: '[data-hero-slider]',
|
|
@@ -30,7 +28,7 @@ const DEFAULT_OPTIONS = {
|
|
|
30
28
|
zIndex: {
|
|
31
29
|
visible: 5,
|
|
32
30
|
next: 4,
|
|
33
|
-
regular: 3
|
|
31
|
+
regular: 3,
|
|
34
32
|
},
|
|
35
33
|
transition: {
|
|
36
34
|
/* how long the actual transition from slide to slide takes */
|
|
@@ -38,31 +36,32 @@ const DEFAULT_OPTIONS = {
|
|
|
38
36
|
/* the transition type. 'parallax' or 'fade' */
|
|
39
37
|
type: 'parallax',
|
|
40
38
|
/* how much to scale when 'idle' */
|
|
41
|
-
scale: 1.05
|
|
39
|
+
scale: 1.05,
|
|
42
40
|
},
|
|
43
41
|
|
|
44
|
-
onTransition: hs => {
|
|
42
|
+
onTransition: (hs) => {
|
|
45
43
|
hs.slide('parallax')
|
|
46
44
|
},
|
|
47
45
|
|
|
48
46
|
onInitialize: (/* hs */) => {},
|
|
49
47
|
|
|
50
48
|
onFadeIn: (hs, callback) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
// Get the first slide's image to start zooming during reveal
|
|
50
|
+
const firstSlideImg = hs.slides[hs._currentSlideIdx].querySelector('.hero-slide-img')
|
|
51
|
+
|
|
52
|
+
// Fade in the container
|
|
53
|
+
animate(hs.el, { opacity: 1 }, { duration: 0.25 })
|
|
54
|
+
|
|
55
|
+
// Start zoom immediately as container fades in
|
|
56
|
+
if (firstSlideImg && hs.slides.length > 1) {
|
|
57
|
+
// Animate with linear easing - MUST specify type: "tween"!
|
|
58
|
+
animate(
|
|
59
|
+
firstSlideImg,
|
|
60
|
+
{ scale: [1, hs.opts.transition.scale] },
|
|
61
|
+
{ type: "tween", duration: hs.opts.interval, ease: "linear" }
|
|
62
|
+
).finished.then(callback)
|
|
64
63
|
}
|
|
65
|
-
}
|
|
64
|
+
},
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
export default class HeroSlider {
|
|
@@ -85,14 +84,15 @@ export default class HeroSlider {
|
|
|
85
84
|
|
|
86
85
|
initialize() {
|
|
87
86
|
this._addResizeHandler()
|
|
87
|
+
this._addVisibilityHandler()
|
|
88
88
|
// style the container
|
|
89
|
-
|
|
89
|
+
set(this.el, {
|
|
90
90
|
position: 'absolute',
|
|
91
91
|
top: 0,
|
|
92
92
|
left: 0,
|
|
93
93
|
width: '100%',
|
|
94
94
|
height: '100%',
|
|
95
|
-
overflow: 'hidden'
|
|
95
|
+
overflow: 'hidden',
|
|
96
96
|
})
|
|
97
97
|
|
|
98
98
|
this.slides = this.el.querySelectorAll('[data-hero-slide]')
|
|
@@ -102,31 +102,39 @@ export default class HeroSlider {
|
|
|
102
102
|
this._currentSlideIdx = this.opts.initialSlideNumber
|
|
103
103
|
|
|
104
104
|
// style the slides
|
|
105
|
-
Array.from(this.slides).forEach(s => {
|
|
106
|
-
|
|
105
|
+
Array.from(this.slides).forEach((s, index) => {
|
|
106
|
+
set(s, {
|
|
107
107
|
zIndex: this.opts.zIndex.regular,
|
|
108
108
|
position: 'absolute',
|
|
109
109
|
top: 0,
|
|
110
110
|
left: 0,
|
|
111
111
|
width: '100%',
|
|
112
|
-
height: '100%'
|
|
112
|
+
height: '100%',
|
|
113
|
+
opacity: index === 0 ? 1 : 0, // Only first slide visible initially
|
|
113
114
|
})
|
|
114
115
|
|
|
115
116
|
const img = s.querySelector('.hero-slide-img')
|
|
116
117
|
|
|
117
118
|
if (img) {
|
|
118
|
-
|
|
119
|
+
set(img, {
|
|
119
120
|
width: document.body.clientWidth,
|
|
120
121
|
height: '100%',
|
|
121
122
|
top: 0,
|
|
122
123
|
left: 0,
|
|
123
|
-
position: 'absolute'
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
transition: 'none',
|
|
126
|
+
transformOrigin: 'center center',
|
|
127
|
+
willChange: 'transform',
|
|
128
|
+
scale: 1, // Initialize scale
|
|
124
129
|
})
|
|
125
130
|
} else {
|
|
126
|
-
console.error(
|
|
131
|
+
console.error(
|
|
132
|
+
'==> JUPITER/HEROSLIDER: MISSING .hero-slide-img INSIDE [data-hero-slide]'
|
|
133
|
+
)
|
|
127
134
|
}
|
|
128
135
|
})
|
|
129
136
|
|
|
137
|
+
// Set proper z-indexes for first two slides
|
|
130
138
|
this.slides[0].style.zIndex = this.opts.zIndex.visible
|
|
131
139
|
if (this.slides[1]) {
|
|
132
140
|
this.slides[1].style.zIndex = this.opts.zIndex.next
|
|
@@ -180,91 +188,147 @@ export default class HeroSlider {
|
|
|
180
188
|
* Switches between slides
|
|
181
189
|
*/
|
|
182
190
|
slide(type) {
|
|
183
|
-
const timeline = gsap.timeline()
|
|
184
|
-
|
|
185
191
|
switch (type) {
|
|
186
192
|
case 'fade':
|
|
187
|
-
|
|
188
|
-
|
|
193
|
+
{
|
|
194
|
+
// Setup: set current slide invisible at correct z-index
|
|
195
|
+
set(this._currentSlide, {
|
|
189
196
|
opacity: 0,
|
|
190
|
-
|
|
191
|
-
zIndex: this.opts.zIndex.visible
|
|
192
|
-
})
|
|
193
|
-
.set(this._nextSlide, {
|
|
194
|
-
opacity: 0
|
|
195
|
-
})
|
|
196
|
-
.to(this._previousSlide, {
|
|
197
|
-
duration: this.opts.interval,
|
|
198
|
-
scale: this.opts.transition.scale
|
|
199
|
-
})
|
|
200
|
-
.to(this._currentSlide, {
|
|
201
|
-
duration: this.opts.transition.duration,
|
|
202
|
-
opacity: 1,
|
|
203
|
-
delay: this.opts.interval - this.opts.transition.duration,
|
|
204
|
-
force3D: true,
|
|
205
|
-
ease: 'sine.inOut'
|
|
197
|
+
zIndex: this.opts.zIndex.visible,
|
|
206
198
|
})
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
set(this._nextSlide, { opacity: 0 })
|
|
200
|
+
|
|
201
|
+
// Get the images to animate
|
|
202
|
+
const previousSlideImg = this._previousSlide.querySelector('.hero-slide-img')
|
|
203
|
+
const currentSlideImg = this._currentSlide.querySelector('.hero-slide-img')
|
|
204
|
+
|
|
205
|
+
// Explicitly set starting scale for current slide
|
|
206
|
+
set(currentSlideImg, { scale: 1 })
|
|
207
|
+
|
|
208
|
+
// Build animation sequence
|
|
209
|
+
// Previous slide continues zooming at same rate during fade out
|
|
210
|
+
// Zoom rate: (scale - 1) / interval, so in transition.duration we zoom by that rate * duration
|
|
211
|
+
const zoomRate = (this.opts.transition.scale - 1) / this.opts.interval
|
|
212
|
+
const continueZoomAmount = zoomRate * this.opts.transition.duration
|
|
213
|
+
const continueZoomTarget = this.opts.transition.scale + continueZoomAmount
|
|
214
|
+
|
|
215
|
+
const sequence = [
|
|
216
|
+
// Previous slide continues zooming during fade at same rate
|
|
217
|
+
[
|
|
218
|
+
previousSlideImg,
|
|
219
|
+
{ scale: [this.opts.transition.scale, continueZoomTarget] },
|
|
220
|
+
{ type: "tween", duration: this.opts.transition.duration, ease: "linear", at: 0 },
|
|
221
|
+
],
|
|
222
|
+
// Current slide fade-in (starts immediately)
|
|
223
|
+
[
|
|
224
|
+
this._currentSlide,
|
|
225
|
+
{ opacity: 1 },
|
|
226
|
+
{
|
|
227
|
+
type: "tween",
|
|
228
|
+
duration: this.opts.transition.duration,
|
|
229
|
+
ease: [0.45, 0, 0.55, 1], // sine.inOut bezier
|
|
230
|
+
at: 0,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
// Current slide image zoom (starts immediately as it fades in)
|
|
234
|
+
[
|
|
235
|
+
currentSlideImg,
|
|
236
|
+
{ scale: [1, this.opts.transition.scale] },
|
|
237
|
+
{ type: "tween", duration: this.opts.interval, ease: "linear", at: 0 },
|
|
238
|
+
],
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
this._currentAnimation = animate(sequence)
|
|
242
|
+
|
|
243
|
+
this._currentAnimation.finished.then(() => {
|
|
244
|
+
// Cleanup after animation completes
|
|
245
|
+
set(this._previousSlide, { opacity: 0 })
|
|
246
|
+
set(this._currentSlide, { opacity: 1 })
|
|
247
|
+
set(previousSlideImg, { scale: 1 })
|
|
248
|
+
this._nextSlide.style.zIndex = this.opts.zIndex.visible
|
|
249
|
+
this._currentSlide.style.zIndex = this.opts.zIndex.regular
|
|
250
|
+
this._previousSlide.style.zIndex = this.opts.zIndex.regular
|
|
251
|
+
this.next()
|
|
209
252
|
})
|
|
210
|
-
|
|
211
|
-
() => {
|
|
212
|
-
this._nextSlide.style.zIndex = this.opts.zIndex.visible
|
|
213
|
-
this._currentSlide.style.zIndex = this.opts.zIndex.regular
|
|
214
|
-
this._previousSlide.style.zIndex = this.opts.zIndex.regular
|
|
215
|
-
this.next()
|
|
216
|
-
},
|
|
217
|
-
null,
|
|
218
|
-
this
|
|
219
|
-
)
|
|
220
|
-
|
|
253
|
+
}
|
|
221
254
|
break
|
|
222
255
|
|
|
223
256
|
case 'parallax':
|
|
224
|
-
|
|
225
|
-
|
|
257
|
+
{
|
|
258
|
+
// Setup: current slide behind previous slide
|
|
259
|
+
set(this._currentSlide, {
|
|
226
260
|
zIndex: this.opts.zIndex.next,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
})
|
|
230
|
-
.fromTo(
|
|
231
|
-
this._previousSlide,
|
|
232
|
-
{
|
|
233
|
-
duration: this.opts.interval,
|
|
234
|
-
overflow: 'hidden'
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
duration: this.opts.interval,
|
|
238
|
-
scale: this.opts.transition.scale
|
|
239
|
-
}
|
|
240
|
-
)
|
|
241
|
-
.to(this._previousSlide, {
|
|
242
|
-
duration: this.opts.transition.duration,
|
|
243
|
-
width: 0,
|
|
244
|
-
ease: 'power3.in',
|
|
245
|
-
autoRound: true,
|
|
246
|
-
overwrite: 'preexisting'
|
|
247
|
-
})
|
|
248
|
-
.set(this._nextSlide, {
|
|
249
|
-
zIndex: this.opts.zIndex.next
|
|
261
|
+
width: '100%',
|
|
262
|
+
opacity: 1, // Make sure it's visible underneath
|
|
250
263
|
})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
set(this._previousSlide, { overflow: 'hidden' })
|
|
265
|
+
|
|
266
|
+
// Get the current slide's image to animate
|
|
267
|
+
const previousSlideImg = this._previousSlide.querySelector('.hero-slide-img')
|
|
268
|
+
const currentSlideImg = this._currentSlide.querySelector('.hero-slide-img')
|
|
269
|
+
|
|
270
|
+
// Explicitly set starting scale for current slide
|
|
271
|
+
set(currentSlideImg, { scale: 1 })
|
|
272
|
+
|
|
273
|
+
// Build animation sequence
|
|
274
|
+
// Previous slide continues zooming at same rate during collapse
|
|
275
|
+
// Zoom rate: (scale - 1) / interval, so in transition.duration we zoom by that rate * duration
|
|
276
|
+
const zoomRate = (this.opts.transition.scale - 1) / this.opts.interval
|
|
277
|
+
const continueZoomAmount = zoomRate * this.opts.transition.duration
|
|
278
|
+
const continueZoomTarget = this.opts.transition.scale + continueZoomAmount
|
|
279
|
+
|
|
280
|
+
const sequence = [
|
|
281
|
+
// Previous slide continues zooming during collapse at same rate
|
|
282
|
+
[
|
|
283
|
+
previousSlideImg,
|
|
284
|
+
{ scale: [this.opts.transition.scale, continueZoomTarget] },
|
|
285
|
+
{ type: "tween", duration: this.opts.transition.duration, ease: "linear", at: 0 },
|
|
286
|
+
],
|
|
287
|
+
// Previous slide width collapse (starts immediately)
|
|
288
|
+
[
|
|
289
|
+
this._previousSlide,
|
|
290
|
+
{ width: 0 },
|
|
291
|
+
{
|
|
292
|
+
type: "tween",
|
|
293
|
+
duration: this.opts.transition.duration,
|
|
294
|
+
ease: [0.895, 0.03, 0.685, 0.22], // power3.in bezier
|
|
295
|
+
at: 0,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
// Current slide image zoom (starts immediately as it's revealed)
|
|
299
|
+
[
|
|
300
|
+
currentSlideImg,
|
|
301
|
+
{ scale: [1, this.opts.transition.scale] },
|
|
302
|
+
{ type: "tween", duration: this.opts.interval, ease: "linear", at: 0 },
|
|
303
|
+
],
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
this._currentAnimation = animate(sequence)
|
|
307
|
+
|
|
308
|
+
this._currentAnimation.finished.then(() => {
|
|
309
|
+
// Cleanup and shuffle z-indexes
|
|
310
|
+
set(this._nextSlide, { zIndex: this.opts.zIndex.next, opacity: 1 })
|
|
311
|
+
set(this._currentSlide, {
|
|
312
|
+
zIndex: this.opts.zIndex.visible,
|
|
313
|
+
width: '100%',
|
|
314
|
+
opacity: 1,
|
|
315
|
+
})
|
|
316
|
+
set(this._previousSlide, {
|
|
317
|
+
zIndex: this.opts.zIndex.regular,
|
|
318
|
+
width: '100%',
|
|
319
|
+
opacity: 0, // Hide previous slide
|
|
320
|
+
})
|
|
321
|
+
// Reset previous slide image scale for next time
|
|
322
|
+
set(previousSlideImg, { scale: 1.0 })
|
|
261
323
|
this.next()
|
|
262
324
|
})
|
|
263
|
-
|
|
325
|
+
}
|
|
264
326
|
break
|
|
265
327
|
|
|
266
328
|
default:
|
|
267
|
-
console.error(
|
|
329
|
+
console.error(
|
|
330
|
+
'==> JUPITER/HEROSLIDER: Unrecognized `opts.transition.type` option.'
|
|
331
|
+
)
|
|
268
332
|
}
|
|
269
333
|
}
|
|
270
334
|
|
|
@@ -272,13 +336,19 @@ export default class HeroSlider {
|
|
|
272
336
|
* Add a window resize handler that resizes slide widths
|
|
273
337
|
*/
|
|
274
338
|
_addResizeHandler() {
|
|
275
|
-
this.observer = new IntersectionObserver(entries => {
|
|
339
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
276
340
|
const [{ isIntersecting }] = entries
|
|
277
341
|
if (isIntersecting) {
|
|
278
342
|
this._resizeSlides()
|
|
279
|
-
window.addEventListener(
|
|
343
|
+
window.addEventListener(
|
|
344
|
+
Events.APPLICATION_RESIZE,
|
|
345
|
+
this._resizeSlides.bind(this)
|
|
346
|
+
)
|
|
280
347
|
} else {
|
|
281
|
-
window.removeEventListener(
|
|
348
|
+
window.removeEventListener(
|
|
349
|
+
Events.APPLICATION_RESIZE,
|
|
350
|
+
this._resizeSlides.bind(this)
|
|
351
|
+
)
|
|
282
352
|
}
|
|
283
353
|
})
|
|
284
354
|
|
|
@@ -286,10 +356,65 @@ export default class HeroSlider {
|
|
|
286
356
|
}
|
|
287
357
|
|
|
288
358
|
_resizeSlides() {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
359
|
+
// Stop any running resize animations
|
|
360
|
+
if (this.resizeAnimation) {
|
|
361
|
+
this.resizeAnimation.stop()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.resizeAnimation = animate(
|
|
365
|
+
this.images,
|
|
366
|
+
{ width: document.body.clientWidth },
|
|
367
|
+
{ duration: 0.15 }
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Add a visibility change handler to restart animations when tab becomes visible
|
|
373
|
+
*/
|
|
374
|
+
_addVisibilityHandler() {
|
|
375
|
+
document.addEventListener('visibilitychange', () => {
|
|
376
|
+
if (document.hidden) {
|
|
377
|
+
// Cancel running animation when tab becomes hidden
|
|
378
|
+
if (this._currentAnimation) {
|
|
379
|
+
this._currentAnimation.cancel()
|
|
380
|
+
this._currentAnimation = null
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
// Reset and restart when tab becomes visible
|
|
384
|
+
this._resetAndRestart()
|
|
385
|
+
}
|
|
293
386
|
})
|
|
294
387
|
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Reset slide states and restart the animation cycle
|
|
391
|
+
*/
|
|
392
|
+
_resetAndRestart() {
|
|
393
|
+
// Reset all slides to initial state
|
|
394
|
+
Array.from(this.slides).forEach((s, index) => {
|
|
395
|
+
const nextSlideIdx = (this._currentSlideIdx + 1) % this.slides.length
|
|
396
|
+
set(s, {
|
|
397
|
+
width: '100%',
|
|
398
|
+
opacity: index === this._currentSlideIdx ? 1 : 0,
|
|
399
|
+
zIndex: index === this._currentSlideIdx ? this.opts.zIndex.visible :
|
|
400
|
+
index === nextSlideIdx ? this.opts.zIndex.next :
|
|
401
|
+
this.opts.zIndex.regular
|
|
402
|
+
})
|
|
403
|
+
const img = s.querySelector('.hero-slide-img')
|
|
404
|
+
if (img) {
|
|
405
|
+
set(img, { scale: 1 })
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// Restart the zoom animation on current slide, then continue cycle
|
|
410
|
+
const currentSlideImg = this.slides[this._currentSlideIdx].querySelector('.hero-slide-img')
|
|
411
|
+
if (currentSlideImg && this.slides.length > 1) {
|
|
412
|
+
this._currentAnimation = animate(
|
|
413
|
+
currentSlideImg,
|
|
414
|
+
{ scale: [1, this.opts.transition.scale] },
|
|
415
|
+
{ type: "tween", duration: this.opts.interval, ease: "linear" }
|
|
416
|
+
)
|
|
417
|
+
this._currentAnimation.finished.then(() => this.next())
|
|
418
|
+
}
|
|
419
|
+
}
|
|
295
420
|
}
|
|
@@ -1,44 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
2
|
* HERO VIDEO
|
|
4
3
|
*
|
|
5
|
-
*
|
|
4
|
+
* Module for handling hero video elements with cover images, play/pause controls,
|
|
5
|
+
* and responsive behavior.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* ## Example
|
|
8
8
|
*
|
|
9
|
+
* const heroVideo = new HeroVideo(app, opts)
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
-
import { CSSPlugin } from 'gsap/CSSPlugin'
|
|
12
|
+
import { animate } from 'motion'
|
|
13
13
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
14
14
|
import * as Events from '../../events'
|
|
15
15
|
import prefersReducedMotion from '../../utils/prefersReducedMotion'
|
|
16
16
|
import imageIsLoaded from '../../utils/imageIsLoaded'
|
|
17
17
|
import Dom from '../Dom'
|
|
18
|
+
import { set, animateAutoAlpha } from '../../utils/motion-helpers'
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} HeroVideoElementGenerators
|
|
22
|
+
* @property {Function} [pause] - Function that returns the pause button HTML
|
|
23
|
+
* @property {Function} [play] - Function that returns the play button HTML
|
|
24
|
+
*/
|
|
20
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} HeroVideoOptions
|
|
28
|
+
* @property {string|HTMLElement} [el='[data-hero-video]'] - Target element selector or element
|
|
29
|
+
* @property {Function} [onFadeIn] - Called when video fades in
|
|
30
|
+
* @property {Function} [onFadeInCover] - Called when cover fades in
|
|
31
|
+
* @property {Function} [onFadeOutCover] - Called when cover fades out
|
|
32
|
+
* @property {Function} [onPlayReady] - Called when video is ready to play
|
|
33
|
+
* @property {Function} [onClickPlay] - Called when play button is clicked
|
|
34
|
+
* @property {Function} [onClickPause] - Called when pause button is clicked
|
|
35
|
+
* @property {string} [pauseParent='.hero-content'] - Selector for parent element to append pause button to
|
|
36
|
+
* @property {HeroVideoElementGenerators} [elements] - Element generators for UI components
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** @type {HeroVideoOptions} */
|
|
21
40
|
const DEFAULT_OPTIONS = {
|
|
22
41
|
el: '[data-hero-video]',
|
|
23
|
-
onFadeIn: hero => {
|
|
24
|
-
|
|
25
|
-
duration: 1,
|
|
26
|
-
autoAlpha: 1
|
|
27
|
-
})
|
|
42
|
+
onFadeIn: (hero) => {
|
|
43
|
+
animateAutoAlpha(hero.videoDiv, 1, { duration: 1 })
|
|
28
44
|
},
|
|
29
45
|
|
|
30
|
-
onFadeInCover: hero => {
|
|
31
|
-
|
|
32
|
-
duration: 0.35,
|
|
33
|
-
autoAlpha: 1
|
|
34
|
-
})
|
|
46
|
+
onFadeInCover: (hero) => {
|
|
47
|
+
animateAutoAlpha(hero.cover, 1, { duration: 0.35 })
|
|
35
48
|
},
|
|
36
49
|
|
|
37
|
-
onFadeOutCover: hero => {
|
|
38
|
-
|
|
39
|
-
duration: 0.35,
|
|
40
|
-
autoAlpha: 0
|
|
41
|
-
})
|
|
50
|
+
onFadeOutCover: (hero) => {
|
|
51
|
+
animateAutoAlpha(hero.cover, 0, { duration: 0.35 })
|
|
42
52
|
},
|
|
43
53
|
|
|
44
54
|
onPlayReady: () => {},
|
|
@@ -57,11 +67,19 @@ const DEFAULT_OPTIONS = {
|
|
|
57
67
|
|
|
58
68
|
play: () => `
|
|
59
69
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 350 350"><circle cx="175" cy="175" r="172.5" stroke="#000" stroke-width="10"/><path stroke="#000" stroke-width="10" d="M240 174l-112-72v148l112-76z"/></svg>
|
|
60
|
-
|
|
61
|
-
}
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
/**
|
|
75
|
+
* HeroVideo component for handling responsive background videos with controls
|
|
76
|
+
*/
|
|
64
77
|
export default class HeroVideo {
|
|
78
|
+
/**
|
|
79
|
+
* Create a new HeroVideo instance
|
|
80
|
+
* @param {Object} app - Application instance
|
|
81
|
+
* @param {HeroVideoOptions} [opts={}] - HeroVideo options
|
|
82
|
+
*/
|
|
65
83
|
constructor(app, opts = {}) {
|
|
66
84
|
this.app = app
|
|
67
85
|
this.booting = true
|
|
@@ -86,18 +104,18 @@ export default class HeroVideo {
|
|
|
86
104
|
initialize() {
|
|
87
105
|
this._addResizeHandler()
|
|
88
106
|
// style the container
|
|
89
|
-
|
|
107
|
+
set(this.el, {
|
|
90
108
|
position: 'absolute',
|
|
91
109
|
top: 0,
|
|
92
110
|
left: 0,
|
|
93
111
|
width: '100%',
|
|
94
112
|
height: '100%',
|
|
95
|
-
overflow: 'hidden'
|
|
113
|
+
overflow: 'hidden',
|
|
96
114
|
})
|
|
97
115
|
|
|
98
116
|
this.cover = Dom.find(this.el, '[data-cover]')
|
|
99
117
|
if (this.cover) {
|
|
100
|
-
|
|
118
|
+
animateAutoAlpha(this.cover, 0, { duration: 0 })
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
const pauseParent = document.querySelector(this.opts.pauseParent)
|
|
@@ -118,27 +136,30 @@ export default class HeroVideo {
|
|
|
118
136
|
this.addEvents()
|
|
119
137
|
this.setSrc()
|
|
120
138
|
|
|
121
|
-
|
|
139
|
+
set(this.videoDiv, {
|
|
122
140
|
position: 'absolute',
|
|
123
141
|
top: 0,
|
|
124
142
|
left: 0,
|
|
125
143
|
width: '100%',
|
|
126
144
|
height: '100%',
|
|
127
|
-
opacity: 0
|
|
145
|
+
opacity: 0,
|
|
128
146
|
})
|
|
129
147
|
|
|
130
148
|
if (!this.video) {
|
|
131
|
-
console.error(
|
|
149
|
+
console.error(
|
|
150
|
+
'==> JUPITER/HEROVIDEO: MISSING <video> INSIDE [data-hero-video-content]'
|
|
151
|
+
)
|
|
132
152
|
return
|
|
133
153
|
}
|
|
134
154
|
this.video.muted = true
|
|
135
155
|
|
|
136
|
-
|
|
137
|
-
width:
|
|
156
|
+
set(this.video, {
|
|
157
|
+
width: '100%',
|
|
138
158
|
height: '100%',
|
|
159
|
+
objectFit: 'cover',
|
|
160
|
+
position: 'absolute',
|
|
139
161
|
top: 0,
|
|
140
162
|
left: 0,
|
|
141
|
-
position: 'absolute'
|
|
142
163
|
})
|
|
143
164
|
|
|
144
165
|
if (this.cover) {
|
|
@@ -149,7 +170,11 @@ export default class HeroVideo {
|
|
|
149
170
|
|
|
150
171
|
window.addEventListener(Events.APPLICATION_READY, () => {
|
|
151
172
|
/* Wait for the video to load, then fade in container element */
|
|
152
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
!this.video.playing &&
|
|
175
|
+
!prefersReducedMotion() &&
|
|
176
|
+
this.video.readyState >= 3
|
|
177
|
+
) {
|
|
153
178
|
this.play()
|
|
154
179
|
this.fadeIn()
|
|
155
180
|
this.booting = false
|
|
@@ -188,12 +213,12 @@ export default class HeroVideo {
|
|
|
188
213
|
this.fadeIn()
|
|
189
214
|
this.booting = false
|
|
190
215
|
} else {
|
|
191
|
-
|
|
216
|
+
set(this.videoDiv, { opacity: 1 })
|
|
192
217
|
}
|
|
193
218
|
}
|
|
194
219
|
})
|
|
195
220
|
if (this.elements.pause) {
|
|
196
|
-
this.elements.pause.addEventListener('click', e => {
|
|
221
|
+
this.elements.pause.addEventListener('click', (e) => {
|
|
197
222
|
e.preventDefault()
|
|
198
223
|
e.stopPropagation()
|
|
199
224
|
if (this.playing) {
|
|
@@ -233,7 +258,7 @@ export default class HeroVideo {
|
|
|
233
258
|
}
|
|
234
259
|
|
|
235
260
|
addObserver() {
|
|
236
|
-
const observer = new IntersectionObserver(entries => {
|
|
261
|
+
const observer = new IntersectionObserver((entries) => {
|
|
237
262
|
const [{ isIntersecting }] = entries
|
|
238
263
|
if (isIntersecting) {
|
|
239
264
|
if (this.forcePaused) {
|
|
@@ -255,13 +280,19 @@ export default class HeroVideo {
|
|
|
255
280
|
* Add a window resize handler that resizes video width
|
|
256
281
|
*/
|
|
257
282
|
_addResizeHandler() {
|
|
258
|
-
this.observer = new IntersectionObserver(entries => {
|
|
283
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
259
284
|
const [{ isIntersecting }] = entries
|
|
260
285
|
if (isIntersecting) {
|
|
261
286
|
this._resize()
|
|
262
|
-
window.addEventListener(
|
|
287
|
+
window.addEventListener(
|
|
288
|
+
Events.APPLICATION_RESIZE,
|
|
289
|
+
this._resize.bind(this)
|
|
290
|
+
)
|
|
263
291
|
} else {
|
|
264
|
-
window.removeEventListener(
|
|
292
|
+
window.removeEventListener(
|
|
293
|
+
Events.APPLICATION_RESIZE,
|
|
294
|
+
this._resize.bind(this)
|
|
295
|
+
)
|
|
265
296
|
}
|
|
266
297
|
})
|
|
267
298
|
|
|
@@ -269,10 +300,7 @@ export default class HeroVideo {
|
|
|
269
300
|
}
|
|
270
301
|
|
|
271
302
|
_resize() {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
width: document.body.clientWidth,
|
|
275
|
-
overwrite: 'all'
|
|
276
|
-
})
|
|
303
|
+
// Video uses object-fit: cover, so no manual sizing needed
|
|
304
|
+
// This method is kept for compatibility but does nothing
|
|
277
305
|
}
|
|
278
306
|
}
|