@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
|
@@ -4,6 +4,25 @@ import imagesAreLoaded from '../../utils/imagesAreLoaded'
|
|
|
4
4
|
import Dom from '../Dom'
|
|
5
5
|
import * as Events from '../../events'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} IntersectionObserverConfig
|
|
9
|
+
* @property {string} [rootMargin] - Margin around the root
|
|
10
|
+
* @property {number} [threshold] - Threshold for intersection
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} LazyloadOptions
|
|
15
|
+
* @property {IntersectionObserverConfig} [revealIntersectionObserverConfig] - Configuration for the reveal intersection observer
|
|
16
|
+
* @property {IntersectionObserverConfig} [loadIntersectionObserverConfig] - Configuration for the load intersection observer
|
|
17
|
+
* @property {IntersectionObserverConfig} [intersectionObserverConfig] - Configuration for general intersection observers
|
|
18
|
+
* @property {boolean} [useNativeLazyloadIfAvailable=true] - Whether to use native lazyloading if available
|
|
19
|
+
* @property {string} [mode='default'] - Lazyload mode
|
|
20
|
+
* @property {number} [minSize=40] - Minimum size for auto sizing
|
|
21
|
+
* @property {boolean} [updateSizes=true] - Whether to update sizes attribute
|
|
22
|
+
* @property {boolean} [registerCallback=true] - Whether to register a callback for APPLICATION_REVEALED event
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** @type {LazyloadOptions} */
|
|
7
26
|
const DEFAULT_OPTIONS = {
|
|
8
27
|
revealIntersectionObserverConfig: {
|
|
9
28
|
rootMargin: '0px 100px 0px 100px',
|
|
@@ -18,12 +37,35 @@ const DEFAULT_OPTIONS = {
|
|
|
18
37
|
minSize: 40,
|
|
19
38
|
updateSizes: true,
|
|
20
39
|
registerCallback: true,
|
|
40
|
+
target: null,
|
|
21
41
|
}
|
|
22
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Lazyload class for handling image lazy loading
|
|
45
|
+
*/
|
|
23
46
|
export default class Lazyload {
|
|
47
|
+
/**
|
|
48
|
+
* Create a new Lazyload instance
|
|
49
|
+
* @param {Object} app - Application instance
|
|
50
|
+
* @param {LazyloadOptions} [opts={}] - Lazyload options
|
|
51
|
+
*/
|
|
24
52
|
constructor(app, opts = {}) {
|
|
25
53
|
this.app = app
|
|
26
54
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
55
|
+
this.target = this.opts.target || document.body
|
|
56
|
+
this.resizePending = new Map()
|
|
57
|
+
this.rafId = null
|
|
58
|
+
|
|
59
|
+
// Create reusable MutationObserver for reveal handling
|
|
60
|
+
this.srcsetReadyObserver = new MutationObserver(mutations => {
|
|
61
|
+
mutations.forEach(record => {
|
|
62
|
+
if (record.type === 'attributes' && record.attributeName === 'data-ll-srcset-ready') {
|
|
63
|
+
this.revealPicture(record.target)
|
|
64
|
+
this.revealObserver.unobserve(record.target)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
27
69
|
this.initialize()
|
|
28
70
|
|
|
29
71
|
if (this.opts.registerCallback) {
|
|
@@ -42,35 +84,30 @@ export default class Lazyload {
|
|
|
42
84
|
}
|
|
43
85
|
|
|
44
86
|
initialize() {
|
|
45
|
-
// initialize
|
|
46
|
-
this.
|
|
87
|
+
// initialize ResizeObserver for images with data-sizes="auto"
|
|
88
|
+
this.initializeResizeObserver()
|
|
47
89
|
// look for lazyload sections. if we find, add an observer that triggers
|
|
48
90
|
// lazyload for all images within.
|
|
49
91
|
this.initializeSections()
|
|
50
92
|
|
|
51
93
|
// if we have native lazyload, use it.
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
) {
|
|
56
|
-
const lazyImages = document.querySelectorAll('[data-ll-image]')
|
|
57
|
-
lazyImages.forEach((img) => {
|
|
94
|
+
if ('loading' in HTMLImageElement.prototype && this.opts.useNativeLazyloadIfAvailable) {
|
|
95
|
+
const lazyImages = this.target.querySelectorAll('[data-ll-image]')
|
|
96
|
+
lazyImages.forEach(img => {
|
|
58
97
|
img.setAttribute('loading', 'lazy')
|
|
59
98
|
this.swapImage(img)
|
|
60
99
|
})
|
|
61
100
|
|
|
62
|
-
const lazyPictures =
|
|
63
|
-
lazyPictures.forEach(
|
|
64
|
-
picture
|
|
65
|
-
.querySelectorAll('img')
|
|
66
|
-
.forEach((img) => img.setAttribute('loading', 'lazy'))
|
|
101
|
+
const lazyPictures = this.target.querySelectorAll('[data-ll-srcset]')
|
|
102
|
+
lazyPictures.forEach(picture => {
|
|
103
|
+
picture.querySelectorAll('img').forEach(img => img.setAttribute('loading', 'lazy'))
|
|
67
104
|
this.swapPicture(picture)
|
|
68
105
|
})
|
|
69
106
|
|
|
70
107
|
return
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
this.lazyPictures =
|
|
110
|
+
this.lazyPictures = this.target.querySelectorAll('[data-ll-srcset]')
|
|
74
111
|
|
|
75
112
|
this.loadObserver = new IntersectionObserver(
|
|
76
113
|
this.handleLoadEntries.bind(this),
|
|
@@ -90,7 +127,7 @@ export default class Lazyload {
|
|
|
90
127
|
this.opts.intersectionObserverConfig
|
|
91
128
|
)
|
|
92
129
|
|
|
93
|
-
this.lazyImages =
|
|
130
|
+
this.lazyImages = this.target.querySelectorAll('[data-ll-image]')
|
|
94
131
|
this.lazyImages.forEach((img, idx) => {
|
|
95
132
|
img.setAttribute('data-ll-blurred', '')
|
|
96
133
|
img.setAttribute('data-ll-idx', idx)
|
|
@@ -103,7 +140,7 @@ export default class Lazyload {
|
|
|
103
140
|
this.lazyPictures.forEach((picture, idx) => {
|
|
104
141
|
if (setAttrs) {
|
|
105
142
|
picture.setAttribute('data-ll-srcset-initialized', '')
|
|
106
|
-
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(
|
|
143
|
+
picture.querySelectorAll('img:not([data-ll-loaded])').forEach(img => {
|
|
107
144
|
img.setAttribute('data-ll-blurred', '')
|
|
108
145
|
img.setAttribute('data-ll-idx', idx)
|
|
109
146
|
img.style.setProperty('--ll-idx', idx)
|
|
@@ -115,45 +152,82 @@ export default class Lazyload {
|
|
|
115
152
|
|
|
116
153
|
forceLoad($container = document.body) {
|
|
117
154
|
const images = Dom.all($container, '[data-ll-image]')
|
|
118
|
-
images.forEach(
|
|
155
|
+
images.forEach(img => this.swapImage(img))
|
|
119
156
|
|
|
120
157
|
const pictures = Dom.all($container, '[data-ll-srcset]')
|
|
121
|
-
pictures.forEach(
|
|
158
|
+
pictures.forEach(picture => this.revealPicture(picture))
|
|
122
159
|
}
|
|
123
160
|
|
|
124
|
-
|
|
125
|
-
if (this.opts.updateSizes) {
|
|
126
|
-
|
|
127
|
-
this.autoSizes()
|
|
128
|
-
window.addEventListener(Events.APPLICATION_RESIZE, () => this.autoSizes())
|
|
161
|
+
initializeResizeObserver() {
|
|
162
|
+
if (!this.opts.updateSizes) {
|
|
163
|
+
return
|
|
129
164
|
}
|
|
130
|
-
}
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
166
|
+
// Use ResizeObserver to watch images with data-sizes="auto"
|
|
167
|
+
// This eliminates layout thrashing from repeated offsetWidth reads
|
|
168
|
+
this.sizeObserver = new ResizeObserver(entries => {
|
|
169
|
+
entries.forEach(entry => {
|
|
170
|
+
const img = entry.target
|
|
171
|
+
// Use contentBoxSize for better performance (avoids layout queries)
|
|
172
|
+
let width = entry.borderBoxSize?.[0]?.inlineSize || entry.contentRect.width
|
|
173
|
+
|
|
174
|
+
// Round to prevent decimal fluctuations causing loops
|
|
175
|
+
width = Math.round(width)
|
|
176
|
+
|
|
177
|
+
// Fallback to minSize if element is too small
|
|
178
|
+
if (width < this.opts.minSize) {
|
|
179
|
+
width = this.opts.minSize
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Only queue update if width actually changed from current sizes attribute
|
|
183
|
+
const currentSizes = img.getAttribute('sizes')
|
|
184
|
+
const expectedSizes = `${width}px`
|
|
185
|
+
|
|
186
|
+
if (currentSizes !== expectedSizes) {
|
|
187
|
+
// Batch updates using RAF to avoid layout thrashing
|
|
188
|
+
this.resizePending.set(img, width)
|
|
189
|
+
|
|
190
|
+
if (!this.rafId) {
|
|
191
|
+
this.rafId = requestAnimationFrame(() => {
|
|
192
|
+
this.flushSizeUpdates()
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Observe all images with data-sizes="auto" within the target container
|
|
200
|
+
const autoSizesImages = Dom.all(this.target, '[data-sizes="auto"]')
|
|
201
|
+
|
|
202
|
+
// Deduplicate in case of multiple Lazyload instances
|
|
203
|
+
const uniqueImages = new Set(autoSizesImages)
|
|
204
|
+
|
|
205
|
+
uniqueImages.forEach(img => {
|
|
206
|
+
this.sizeObserver.observe(img)
|
|
144
207
|
})
|
|
145
208
|
}
|
|
146
209
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
210
|
+
flushSizeUpdates() {
|
|
211
|
+
// Batch all size updates together to minimize reflows
|
|
212
|
+
this.resizePending.forEach((width, img) => {
|
|
213
|
+
const currentSizes = img.getAttribute('sizes')
|
|
214
|
+
const newSizes = `${Math.round(width)}px`
|
|
150
215
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
216
|
+
// Only update if value actually changed to prevent resize loops
|
|
217
|
+
if (currentSizes !== newSizes) {
|
|
218
|
+
img.setAttribute('sizes', newSizes)
|
|
219
|
+
if (img.parentNode) {
|
|
220
|
+
Array.from(Dom.all(img.parentNode, 'source')).forEach(source => {
|
|
221
|
+
if (source.getAttribute('sizes') !== newSizes) {
|
|
222
|
+
source.setAttribute('sizes', newSizes)
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})
|
|
155
228
|
|
|
156
|
-
|
|
229
|
+
this.resizePending.clear()
|
|
230
|
+
this.rafId = null
|
|
157
231
|
}
|
|
158
232
|
|
|
159
233
|
initializeSections() {
|
|
@@ -162,12 +236,12 @@ export default class Lazyload {
|
|
|
162
236
|
const sectionObserver = (section, children) => {
|
|
163
237
|
const imagesInSection = Dom.all(section, 'img')
|
|
164
238
|
return new IntersectionObserver((entries, self) => {
|
|
165
|
-
entries.forEach(
|
|
239
|
+
entries.forEach(entry => {
|
|
166
240
|
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
167
241
|
imagesAreLoaded(imagesInSection, true).then(() => {
|
|
168
242
|
dispatchElementEvent(section, Events.SECTION_LAZYLOADED)
|
|
169
243
|
})
|
|
170
|
-
children.forEach(
|
|
244
|
+
children.forEach(picture => {
|
|
171
245
|
this.loadPicture(picture)
|
|
172
246
|
this.loadObserver.unobserve(picture)
|
|
173
247
|
})
|
|
@@ -177,7 +251,7 @@ export default class Lazyload {
|
|
|
177
251
|
}, this.opts.intersectionObserverConfig)
|
|
178
252
|
}
|
|
179
253
|
|
|
180
|
-
sections.forEach(
|
|
254
|
+
sections.forEach(section => {
|
|
181
255
|
const children = section.querySelectorAll('picture')
|
|
182
256
|
const obs = sectionObserver(section, children)
|
|
183
257
|
obs.observe(section)
|
|
@@ -187,7 +261,7 @@ export default class Lazyload {
|
|
|
187
261
|
|
|
188
262
|
// we load the picture a ways before it enters the viewport
|
|
189
263
|
handleLoadEntries(elements) {
|
|
190
|
-
elements.forEach(
|
|
264
|
+
elements.forEach(item => {
|
|
191
265
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
192
266
|
const picture = item.target
|
|
193
267
|
this.loadPicture(picture)
|
|
@@ -198,26 +272,15 @@ export default class Lazyload {
|
|
|
198
272
|
|
|
199
273
|
// we reveal the picture when it enters the viewport
|
|
200
274
|
handleRevealEntries(elements) {
|
|
201
|
-
|
|
202
|
-
mutations.forEach((record) => {
|
|
203
|
-
if (
|
|
204
|
-
record.type === 'attributes' &&
|
|
205
|
-
record.attributeName === 'data-ll-srcset-ready'
|
|
206
|
-
) {
|
|
207
|
-
this.revealPicture(record.target)
|
|
208
|
-
this.revealObserver.unobserve(record.target)
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
elements.forEach((item) => {
|
|
275
|
+
elements.forEach(item => {
|
|
214
276
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
215
277
|
const picture = item.target
|
|
216
278
|
const ready = item.target.hasAttribute('data-ll-srcset-ready')
|
|
217
279
|
if (!ready) {
|
|
218
280
|
// element is not loaded, observe the picture and wait for
|
|
219
281
|
// `data-ll-srcset-ready` before revealing
|
|
220
|
-
|
|
282
|
+
// Use reusable MutationObserver to prevent memory leaks
|
|
283
|
+
this.srcsetReadyObserver.observe(picture, { attributes: true })
|
|
221
284
|
} else {
|
|
222
285
|
this.revealPicture(picture)
|
|
223
286
|
this.revealObserver.unobserve(item.target)
|
|
@@ -249,23 +312,8 @@ export default class Lazyload {
|
|
|
249
312
|
const img = picture.querySelector('img')
|
|
250
313
|
|
|
251
314
|
const onload = () => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
this.app.browser === 'firefox'
|
|
255
|
-
) {
|
|
256
|
-
// set sizes attribute on load again,
|
|
257
|
-
// since firefox sometimes is a bit slow to
|
|
258
|
-
// get the actual image width
|
|
259
|
-
const width = this.getWidth(img)
|
|
260
|
-
|
|
261
|
-
img.setAttribute('sizes', `${width}px`)
|
|
262
|
-
if (img.parentNode) {
|
|
263
|
-
Array.from(Dom.all(img.parentNode, 'source')).forEach((source) =>
|
|
264
|
-
source.setAttribute('sizes', `${width}px`)
|
|
265
|
-
)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
315
|
+
// ResizeObserver now handles size updates automatically,
|
|
316
|
+
// including Firefox's delayed dimension calculation
|
|
269
317
|
img.removeAttribute('data-ll-placeholder')
|
|
270
318
|
img.removeAttribute('data-ll-blurred')
|
|
271
319
|
img.removeAttribute('data-ll-loading')
|
|
@@ -309,7 +357,7 @@ export default class Lazyload {
|
|
|
309
357
|
}
|
|
310
358
|
|
|
311
359
|
lazyloadImages(elements) {
|
|
312
|
-
elements.forEach(
|
|
360
|
+
elements.forEach(item => {
|
|
313
361
|
if (item.isIntersecting || item.intersectionRatio > 0) {
|
|
314
362
|
const image = item.target
|
|
315
363
|
this.swapImage(image)
|
|
@@ -1,9 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { gsap } from 'gsap'
|
|
1
|
+
import { animate } from 'motion'
|
|
3
2
|
import _defaultsDeep from 'lodash.defaultsdeep'
|
|
4
3
|
import imageIsLoaded from '../../utils/imageIsLoaded'
|
|
5
4
|
import Dom from '../Dom'
|
|
6
|
-
|
|
5
|
+
import { set, PausedTimeline } from '../../utils/motion-helpers'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} LightboxElements
|
|
9
|
+
* @property {Function} [arrowRight] - Function to create right arrow element
|
|
10
|
+
* @property {Function} [arrowLeft] - Function to create left arrow element
|
|
11
|
+
* @property {Function} [close] - Function to create close button element
|
|
12
|
+
* @property {Function} [dot] - Function to create dot/indicator element
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} LightboxOptions
|
|
17
|
+
* @property {boolean} [captions=false] - Enable captions
|
|
18
|
+
* @property {boolean} [numbers=false] - Enable index numbers
|
|
19
|
+
* @property {string|boolean} [trigger=false] - Selector for trigger element to open the lightbox
|
|
20
|
+
* @property {LightboxElements} [elements] - Custom elements configuration
|
|
21
|
+
* @property {Function} [onClick] - Click handler for lightbox
|
|
22
|
+
* @property {Function} [onPointerLeft] - Called when pointer moves left
|
|
23
|
+
* @property {Function} [onPointerRight] - Called when pointer moves right
|
|
24
|
+
* @property {Function} [onCaptionOut] - Called when caption fades out
|
|
25
|
+
* @property {Function} [onCaptionIn] - Called when caption fades in
|
|
26
|
+
* @property {Function} [onImageOut] - Called when image fades out
|
|
27
|
+
* @property {Function} [onImageIn] - Called when image fades in
|
|
28
|
+
* @property {Function} [onNumbers] - Called when numbers display updates
|
|
29
|
+
* @property {Function} [onBeforeOpen] - Called before opening lightbox
|
|
30
|
+
* @property {Function} [onOpen] - Called when lightbox opens
|
|
31
|
+
* @property {Function} [onAfterClose] - Called after lightbox closes
|
|
32
|
+
* @property {Function} [onClose] - Called when lightbox closes
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** @type {LightboxOptions} */
|
|
7
36
|
const DEFAULT_OPTIONS = {
|
|
8
37
|
/* enable captions */
|
|
9
38
|
captions: false,
|
|
@@ -11,9 +40,6 @@ const DEFAULT_OPTIONS = {
|
|
|
11
40
|
/* enable index numbers */
|
|
12
41
|
numbers: false,
|
|
13
42
|
|
|
14
|
-
/* enable swipe — this breaks native zoom! */
|
|
15
|
-
swipe: true,
|
|
16
|
-
|
|
17
43
|
/* set to a selector if you want a specific trigger element to open the box */
|
|
18
44
|
trigger: false,
|
|
19
45
|
|
|
@@ -33,7 +59,7 @@ const DEFAULT_OPTIONS = {
|
|
|
33
59
|
},
|
|
34
60
|
|
|
35
61
|
close: () => document.createTextNode('×'),
|
|
36
|
-
dot: () => document.createTextNode('▪')
|
|
62
|
+
dot: () => document.createTextNode('▪'),
|
|
37
63
|
},
|
|
38
64
|
|
|
39
65
|
onClick: (lightbox, section, e) => {
|
|
@@ -56,7 +82,10 @@ const DEFAULT_OPTIONS = {
|
|
|
56
82
|
return
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
lightbox.timelines.caption.to(lightbox.elements.caption, {
|
|
85
|
+
lightbox.timelines.caption.to(lightbox.elements.caption, {
|
|
86
|
+
duration: 0.4,
|
|
87
|
+
autoAlpha: 0,
|
|
88
|
+
})
|
|
60
89
|
},
|
|
61
90
|
|
|
62
91
|
onCaptionIn: (lightbox, captionHasChanged) => {
|
|
@@ -64,16 +93,26 @@ const DEFAULT_OPTIONS = {
|
|
|
64
93
|
return
|
|
65
94
|
}
|
|
66
95
|
|
|
67
|
-
lightbox.timelines.caption.to(lightbox.elements.caption, {
|
|
96
|
+
lightbox.timelines.caption.to(lightbox.elements.caption, {
|
|
97
|
+
duration: 0.4,
|
|
98
|
+
autoAlpha: 1,
|
|
99
|
+
})
|
|
68
100
|
},
|
|
69
101
|
|
|
70
|
-
onImageOut: lightbox => {
|
|
71
|
-
lightbox.timelines.image.to(lightbox.currentImage, {
|
|
102
|
+
onImageOut: (lightbox) => {
|
|
103
|
+
lightbox.timelines.image.to(lightbox.currentImage, {
|
|
104
|
+
duration: 0.5,
|
|
105
|
+
autoAlpha: 0,
|
|
106
|
+
})
|
|
72
107
|
},
|
|
73
108
|
|
|
74
|
-
onImageIn: lightbox => {
|
|
109
|
+
onImageIn: (lightbox) => {
|
|
75
110
|
const delay = lightbox.firstTransition ? 0.6 : 0.4
|
|
76
|
-
lightbox.timelines.image.to(lightbox.nextImage, {
|
|
111
|
+
lightbox.timelines.image.to(lightbox.nextImage, {
|
|
112
|
+
duration: 0.5,
|
|
113
|
+
autoAlpha: 1,
|
|
114
|
+
delay,
|
|
115
|
+
})
|
|
77
116
|
},
|
|
78
117
|
|
|
79
118
|
onNumbers: (lightbox, section) => {
|
|
@@ -82,52 +121,47 @@ const DEFAULT_OPTIONS = {
|
|
|
82
121
|
|
|
83
122
|
onBeforeOpen: () => {},
|
|
84
123
|
|
|
85
|
-
onOpen: h => {
|
|
124
|
+
onOpen: (h) => {
|
|
86
125
|
h.app.scrollLock()
|
|
87
126
|
|
|
88
|
-
|
|
89
|
-
duration: 0.5,
|
|
90
|
-
opacity: 1
|
|
91
|
-
})
|
|
127
|
+
animate(h.elements.wrapper, { opacity: 1 }, { duration: 0.5 })
|
|
92
128
|
},
|
|
93
129
|
|
|
94
130
|
onAfterClose: () => {},
|
|
95
131
|
|
|
96
|
-
onClose: h => {
|
|
132
|
+
onClose: (h) => {
|
|
97
133
|
if (h.opts.captions) {
|
|
98
|
-
|
|
99
|
-
duration: 0.45,
|
|
100
|
-
opacity: 0
|
|
101
|
-
})
|
|
134
|
+
animate(h.elements.caption, { opacity: 0 }, { duration: 0.45 })
|
|
102
135
|
}
|
|
103
136
|
|
|
104
|
-
|
|
137
|
+
animate(
|
|
105
138
|
[
|
|
106
139
|
h.elements.imgWrapper,
|
|
107
140
|
h.elements.nextArrow,
|
|
108
141
|
h.elements.prevArrow,
|
|
109
142
|
h.elements.close,
|
|
110
|
-
h.elements.dots
|
|
143
|
+
h.elements.dots,
|
|
111
144
|
],
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
h.destroy()
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
}
|
|
145
|
+
{ opacity: 0 },
|
|
146
|
+
{ duration: 0.5 }
|
|
147
|
+
).finished.then(() => {
|
|
148
|
+
animate(h.elements.wrapper, { opacity: 0 }, { duration: 0.45 }).finished.then(() => {
|
|
149
|
+
h.app.scrollRelease()
|
|
150
|
+
h.destroy()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
},
|
|
128
154
|
}
|
|
129
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Lightbox component for displaying images in a fullscreen overlay
|
|
158
|
+
*/
|
|
130
159
|
export default class Lightbox {
|
|
160
|
+
/**
|
|
161
|
+
* Create a new Lightbox instance
|
|
162
|
+
* @param {Object} app - Application instance
|
|
163
|
+
* @param {LightboxOptions} [opts={}] - Lightbox options
|
|
164
|
+
*/
|
|
131
165
|
constructor(app, opts = {}) {
|
|
132
166
|
this.app = app
|
|
133
167
|
this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
|
|
@@ -140,16 +174,17 @@ export default class Lightbox {
|
|
|
140
174
|
this.firstTransition = true
|
|
141
175
|
this.previousCaption = null
|
|
142
176
|
this.timelines = {
|
|
143
|
-
caption:
|
|
144
|
-
image:
|
|
177
|
+
caption: new PausedTimeline(),
|
|
178
|
+
image: new PausedTimeline(),
|
|
145
179
|
}
|
|
146
180
|
|
|
147
|
-
this.lightboxes.forEach(lightbox => {
|
|
181
|
+
this.lightboxes.forEach((lightbox) => {
|
|
148
182
|
const href = lightbox.getAttribute('data-lightbox')
|
|
149
183
|
const srcset = lightbox.getAttribute('data-srcset')
|
|
150
184
|
const originalImage = lightbox.querySelector('img')
|
|
151
185
|
const alt = originalImage.getAttribute('alt')
|
|
152
|
-
const section =
|
|
186
|
+
const section =
|
|
187
|
+
lightbox.getAttribute('data-lightbox-section') || 'general'
|
|
153
188
|
let trigger = lightbox
|
|
154
189
|
if (this.opts.trigger) {
|
|
155
190
|
trigger = Dom.find(lightbox, this.opts.trigger) || lightbox
|
|
@@ -162,12 +197,12 @@ export default class Lightbox {
|
|
|
162
197
|
const image = {
|
|
163
198
|
href,
|
|
164
199
|
alt,
|
|
165
|
-
srcset
|
|
200
|
+
srcset,
|
|
166
201
|
}
|
|
167
202
|
|
|
168
203
|
const index = this.sections[section].push(image) - 1
|
|
169
204
|
|
|
170
|
-
trigger.addEventListener('click', e => {
|
|
205
|
+
trigger.addEventListener('click', (e) => {
|
|
171
206
|
e.preventDefault()
|
|
172
207
|
this.showBox(section, index)
|
|
173
208
|
})
|
|
@@ -207,30 +242,30 @@ export default class Lightbox {
|
|
|
207
242
|
this.elements.nextArrow.appendChild(this.opts.elements.arrowRight())
|
|
208
243
|
this.elements.nextArrow.href = '#'
|
|
209
244
|
|
|
210
|
-
this.elements.nextArrow.addEventListener('click', e => {
|
|
245
|
+
this.elements.nextArrow.addEventListener('click', (e) => {
|
|
211
246
|
e.stopPropagation()
|
|
212
247
|
e.preventDefault()
|
|
213
248
|
this.setImg(section, this.getNextIdx(section))
|
|
214
249
|
})
|
|
215
250
|
|
|
216
|
-
this.elements.prevArrow.addEventListener('click', e => {
|
|
251
|
+
this.elements.prevArrow.addEventListener('click', (e) => {
|
|
217
252
|
e.stopPropagation()
|
|
218
253
|
e.preventDefault()
|
|
219
254
|
this.setImg(section, this.getPrevIdx(section))
|
|
220
255
|
})
|
|
221
256
|
|
|
222
|
-
this.keyUpCallback = event => {
|
|
257
|
+
this.keyUpCallback = (event) => {
|
|
223
258
|
this.onKeyup(event, section)
|
|
224
259
|
}
|
|
225
260
|
|
|
226
261
|
document.removeEventListener('keyup', this.keyUpCallback)
|
|
227
262
|
document.addEventListener('keyup', this.keyUpCallback)
|
|
228
263
|
|
|
229
|
-
this.elements.wrapper.addEventListener('mousemove', event => {
|
|
264
|
+
this.elements.wrapper.addEventListener('mousemove', (event) => {
|
|
230
265
|
this.onMouseMove(event)
|
|
231
266
|
})
|
|
232
267
|
|
|
233
|
-
this.elements.wrapper.addEventListener('click', event => {
|
|
268
|
+
this.elements.wrapper.addEventListener('click', (event) => {
|
|
234
269
|
this.onClick(event, section)
|
|
235
270
|
})
|
|
236
271
|
|
|
@@ -242,7 +277,8 @@ export default class Lightbox {
|
|
|
242
277
|
|
|
243
278
|
this.sections[section].forEach((img, x) => {
|
|
244
279
|
const imgElement = document.createElement('img')
|
|
245
|
-
|
|
280
|
+
set(imgElement, { opacity: 0 })
|
|
281
|
+
imgElement.style.visibility = 'hidden'
|
|
246
282
|
imgElement.classList.add('lightbox-image', 'm-lg')
|
|
247
283
|
imgElement.setAttribute('data-idx', x)
|
|
248
284
|
this.elements.imgWrapper.appendChild(imgElement)
|
|
@@ -257,7 +293,7 @@ export default class Lightbox {
|
|
|
257
293
|
activeLink = a
|
|
258
294
|
}
|
|
259
295
|
|
|
260
|
-
a.addEventListener('click', e => {
|
|
296
|
+
a.addEventListener('click', (e) => {
|
|
261
297
|
a.classList.add('active')
|
|
262
298
|
activeLink.classList.remove('active')
|
|
263
299
|
activeLink = a
|
|
@@ -290,13 +326,10 @@ export default class Lightbox {
|
|
|
290
326
|
document.body.appendChild(this.elements.wrapper)
|
|
291
327
|
|
|
292
328
|
this.setImg(section, index, this.getPrevIdx(section))
|
|
293
|
-
if (this.opts.swipe) {
|
|
294
|
-
this.attachSwiper(section, this.elements.content, index)
|
|
295
|
-
}
|
|
296
329
|
|
|
297
330
|
this.opts.onOpen(this)
|
|
298
331
|
|
|
299
|
-
this.elements.close.addEventListener('click', e => {
|
|
332
|
+
this.elements.close.addEventListener('click', (e) => {
|
|
300
333
|
e.preventDefault()
|
|
301
334
|
e.stopPropagation()
|
|
302
335
|
|
|
@@ -334,7 +367,8 @@ export default class Lightbox {
|
|
|
334
367
|
activeDot.classList.add('active')
|
|
335
368
|
|
|
336
369
|
if (this.elements.caption) {
|
|
337
|
-
captionHasChanged =
|
|
370
|
+
captionHasChanged =
|
|
371
|
+
this.previousCaption !== this.sections[section][index].alt
|
|
338
372
|
this.previousCaption = this.sections[section][index].alt
|
|
339
373
|
this.opts.onCaptionOut(this, captionHasChanged)
|
|
340
374
|
this.timelines.caption.call(() => {
|
|
@@ -357,7 +391,10 @@ export default class Lightbox {
|
|
|
357
391
|
if (this.imgs[index + x]) {
|
|
358
392
|
this.imgs[index + x].src = this.sections[section][index + x].href
|
|
359
393
|
if (this.sections[section][index + x].srcset) {
|
|
360
|
-
this.imgs[index + x].setAttribute(
|
|
394
|
+
this.imgs[index + x].setAttribute(
|
|
395
|
+
'srcset',
|
|
396
|
+
this.sections[section][index + x].srcset
|
|
397
|
+
)
|
|
361
398
|
}
|
|
362
399
|
} else {
|
|
363
400
|
break
|
|
@@ -367,7 +404,10 @@ export default class Lightbox {
|
|
|
367
404
|
this.nextImage = this.imgs[index]
|
|
368
405
|
this.nextImage.src = this.sections[section][index].href
|
|
369
406
|
if (this.sections[section][index].srcset) {
|
|
370
|
-
this.nextImage.setAttribute(
|
|
407
|
+
this.nextImage.setAttribute(
|
|
408
|
+
'srcset',
|
|
409
|
+
this.sections[section][index].srcset
|
|
410
|
+
)
|
|
371
411
|
}
|
|
372
412
|
|
|
373
413
|
this.opts.onImageIn(this)
|
|
@@ -442,23 +482,4 @@ export default class Lightbox {
|
|
|
442
482
|
this.opts.onPointerRight(this)
|
|
443
483
|
}
|
|
444
484
|
}
|
|
445
|
-
|
|
446
|
-
attachSwiper(section, el, initialIdx) {
|
|
447
|
-
const hammerManager = new Manager(el)
|
|
448
|
-
const swipeHandler = new Swipe()
|
|
449
|
-
|
|
450
|
-
this.elements.content.setAttribute('data-current-idx', initialIdx)
|
|
451
|
-
|
|
452
|
-
hammerManager.add(swipeHandler)
|
|
453
|
-
|
|
454
|
-
hammerManager.on('swipeleft', () => {
|
|
455
|
-
const index = this.getNextIdx(section)
|
|
456
|
-
this.setImg(section, index)
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
hammerManager.on('swiperight', () => {
|
|
460
|
-
const index = this.getPrevIdx(section)
|
|
461
|
-
this.setImg(section, index)
|
|
462
|
-
})
|
|
463
|
-
}
|
|
464
485
|
}
|