chameleon-backgrounds 2.1.2 → 3.0.0
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 +67 -10
- package/dist/chameleon-backgrounds.js +652 -460
- package/dist/chameleon-backgrounds.js.map +23 -1
- package/dist/chameleon-backgrounds.min.js +1 -1
- package/dist/chameleon-backgrounds.min.js.LICENSE.txt +1 -1
- package/package.json +1 -1
- package/src/chameleon-backgrounds.js +274 -98
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* |___/
|
|
15
15
|
*
|
|
16
16
|
* @module ChameleonBackgrounds
|
|
17
|
-
* @version
|
|
17
|
+
* @version 3.0.0
|
|
18
18
|
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
19
19
|
* @license MIT
|
|
20
20
|
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
@@ -24,16 +24,24 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
+
* @typedef {Object} ChameleonImageConfig
|
|
28
|
+
* @property {string} url - The fallback URL.
|
|
29
|
+
* @property {string} [srcset] - Optional srcset.
|
|
30
|
+
* @property {string} [sizes] - Optional sizes.
|
|
31
|
+
*
|
|
27
32
|
* @typedef {Object} ChameleonOptions
|
|
28
33
|
* @property {string|HTMLElement} element - CSS selector or DOM element to attach to.
|
|
29
34
|
* @property {'single'|'slider'} type - Background mode.
|
|
30
|
-
* @property {string|string[]}
|
|
35
|
+
* @property {string|string[]|ChameleonImageConfig|ChameleonImageConfig[]} src - Image URL(s) or config object(s).
|
|
31
36
|
* @property {string} overlayColor - Overlay color (hex, rgb, rgba, hsl).
|
|
32
37
|
* @property {string} [overlayImage] - Optional overlay pattern image URL.
|
|
33
38
|
* @property {number} [minOverlay=0] - Minimum overlay opacity after fade (0–1).
|
|
34
39
|
* @property {number} [transitionDuration=2000] - Fade duration in milliseconds.
|
|
35
40
|
* @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.
|
|
36
41
|
* @property {boolean} [sliderLoop=false] - Whether the slider restarts after the last slide.
|
|
42
|
+
* @property {'high'|'low'|'auto'} [fetchPriority='auto'] - fetchPriority for the first loaded image (e.g., 'high' for LCP images).
|
|
43
|
+
* @property {boolean} [lazyLoad=true] - Whether to wait until element is in viewport using IntersectionObserver.
|
|
44
|
+
* @property {'solid'|'crossfade'} [transitionMode='solid'] - Transition mode: 'solid' (fade to color) or 'crossfade' (fade between images).
|
|
37
45
|
*/
|
|
38
46
|
|
|
39
47
|
class ChameleonBackgrounds {
|
|
@@ -48,6 +56,10 @@ class ChameleonBackgrounds {
|
|
|
48
56
|
transitionDuration: 2000,
|
|
49
57
|
sliderDuration: 8000,
|
|
50
58
|
sliderLoop: false,
|
|
59
|
+
fetchPriority: 'auto',
|
|
60
|
+
lazyLoad: true,
|
|
61
|
+
transitionMode: 'solid',
|
|
62
|
+
respectReducedMotion: false,
|
|
51
63
|
});
|
|
52
64
|
|
|
53
65
|
// Legacy snake_case → camelCase alias map
|
|
@@ -58,8 +70,15 @@ class ChameleonBackgrounds {
|
|
|
58
70
|
min_overlay: 'minOverlay',
|
|
59
71
|
overlay_color: 'overlayColor',
|
|
60
72
|
overlay_image: 'overlayImage',
|
|
73
|
+
fetch_priority: 'fetchPriority',
|
|
74
|
+
lazy_load: 'lazyLoad',
|
|
75
|
+
transition_mode: 'transitionMode',
|
|
76
|
+
respect_reduced_motion: 'respectReducedMotion',
|
|
61
77
|
});
|
|
62
78
|
|
|
79
|
+
/** @type {boolean} */
|
|
80
|
+
static #globalStylesInjected = false;
|
|
81
|
+
|
|
63
82
|
/** @type {ChameleonOptions} */
|
|
64
83
|
#options;
|
|
65
84
|
|
|
@@ -72,12 +91,18 @@ class ChameleonBackgrounds {
|
|
|
72
91
|
/** @type {string} */
|
|
73
92
|
#originalHTML;
|
|
74
93
|
|
|
75
|
-
/** @type {HTMLStyleElement|null} */
|
|
76
|
-
#styleElement = null;
|
|
77
|
-
|
|
78
94
|
/** @type {number|null} */
|
|
79
95
|
#sliderIntervalId = null;
|
|
80
96
|
|
|
97
|
+
/** @type {number} */
|
|
98
|
+
#currentSlideIndex = 0;
|
|
99
|
+
|
|
100
|
+
/** @type {boolean} */
|
|
101
|
+
#isPaused = false;
|
|
102
|
+
|
|
103
|
+
/** @type {IntersectionObserver|null} */
|
|
104
|
+
#observer = null;
|
|
105
|
+
|
|
81
106
|
/** @type {boolean} */
|
|
82
107
|
#destroyed = false;
|
|
83
108
|
|
|
@@ -113,12 +138,14 @@ class ChameleonBackgrounds {
|
|
|
113
138
|
this.#sliderIntervalId = null;
|
|
114
139
|
}
|
|
115
140
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.#
|
|
119
|
-
this.#styleElement = null;
|
|
141
|
+
if (this.#observer) {
|
|
142
|
+
this.#observer.disconnect();
|
|
143
|
+
this.#observer = null;
|
|
120
144
|
}
|
|
121
145
|
|
|
146
|
+
// Clean up dynamic classes on host
|
|
147
|
+
this.#element.classList.remove(`cbg-host-${this.#uid}`, 'cbg-host');
|
|
148
|
+
|
|
122
149
|
// Unwrap the original content instead of resetting innerHTML
|
|
123
150
|
const wrapper = this.#element.querySelector(`#cbg-inner-${this.#uid}`);
|
|
124
151
|
if (wrapper && wrapper.parentNode === this.#element) {
|
|
@@ -134,8 +161,16 @@ class ChameleonBackgrounds {
|
|
|
134
161
|
loader.remove();
|
|
135
162
|
}
|
|
136
163
|
|
|
137
|
-
// Remove
|
|
164
|
+
// Remove temporary crossfade elements
|
|
165
|
+
const crossfadeElements = this.#element.querySelectorAll(`.cbg-crossfade-${this.#uid}`);
|
|
166
|
+
crossfadeElements.forEach(el => el.remove());
|
|
167
|
+
|
|
168
|
+
// Remove inline CSS Variables and background-image
|
|
138
169
|
this.#element.style.backgroundImage = '';
|
|
170
|
+
this.#element.style.removeProperty('--cbg-duration');
|
|
171
|
+
this.#element.style.removeProperty('--cbg-overlay-color');
|
|
172
|
+
this.#element.style.removeProperty('--cbg-overlay-image');
|
|
173
|
+
this.#element.style.removeProperty('--cbg-min-overlay');
|
|
139
174
|
}
|
|
140
175
|
|
|
141
176
|
/**
|
|
@@ -151,71 +186,145 @@ class ChameleonBackgrounds {
|
|
|
151
186
|
// ---------------------------------------------------------------------------
|
|
152
187
|
|
|
153
188
|
#init() {
|
|
154
|
-
this.#
|
|
189
|
+
this.#injectGlobalStyles();
|
|
190
|
+
this.#updateCSSVariables();
|
|
155
191
|
this.#buildDOM();
|
|
156
192
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
// Check for prefers-reduced-motion to auto-pause slider if enabled
|
|
194
|
+
if (this.#options.respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
195
|
+
this.#isPaused = true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.#options.lazyLoad && 'IntersectionObserver' in window) {
|
|
199
|
+
this.#observer = new IntersectionObserver((entries) => {
|
|
200
|
+
entries.forEach((entry) => {
|
|
201
|
+
if (entry.isIntersecting) {
|
|
202
|
+
this.#observer.disconnect();
|
|
203
|
+
this.#observer = null;
|
|
204
|
+
this.#retrieveBackground();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
this.#observer.observe(this.#element);
|
|
163
209
|
} else {
|
|
164
|
-
if (
|
|
165
|
-
this.#retrieveBackground();
|
|
210
|
+
if (this.#element.matches('body')) {
|
|
211
|
+
queueMicrotask(() => this.#retrieveBackground());
|
|
166
212
|
} else {
|
|
167
|
-
|
|
213
|
+
if (document.readyState === 'complete') {
|
|
214
|
+
this.#retrieveBackground();
|
|
215
|
+
} else {
|
|
216
|
+
window.addEventListener('load', () => this.#retrieveBackground(), { once: true });
|
|
217
|
+
}
|
|
168
218
|
}
|
|
169
219
|
}
|
|
170
220
|
}
|
|
171
221
|
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Play / Pause API
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Pause the slideshow.
|
|
228
|
+
*/
|
|
229
|
+
pause() {
|
|
230
|
+
if (this.#destroyed || this.#options.type !== 'slider') return;
|
|
231
|
+
this.#isPaused = true;
|
|
232
|
+
if (this.#sliderIntervalId !== null) {
|
|
233
|
+
clearInterval(this.#sliderIntervalId);
|
|
234
|
+
this.#sliderIntervalId = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Resume the slideshow.
|
|
240
|
+
*/
|
|
241
|
+
play() {
|
|
242
|
+
if (this.#destroyed || this.#options.type !== 'slider') return;
|
|
243
|
+
this.#isPaused = false;
|
|
244
|
+
// Only restart if not currently running
|
|
245
|
+
if (this.#sliderIntervalId === null) {
|
|
246
|
+
this.#startSliderLoop();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
172
250
|
// ---------------------------------------------------------------------------
|
|
173
251
|
// DOM Construction
|
|
174
252
|
// ---------------------------------------------------------------------------
|
|
175
253
|
|
|
176
254
|
/**
|
|
177
|
-
* Inject
|
|
255
|
+
* Inject global styles once.
|
|
178
256
|
*/
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
? 'body'
|
|
183
|
-
: this.#options.element;
|
|
184
|
-
const duration = this.#options.transitionDuration / 1000; // ms → s
|
|
185
|
-
const position = this.#element.matches('body') ? 'fixed' : 'absolute';
|
|
186
|
-
const overlayBg = this.#options.overlayImage
|
|
187
|
-
? `url(${this.#options.overlayImage})`
|
|
188
|
-
: 'none';
|
|
257
|
+
#injectGlobalStyles() {
|
|
258
|
+
if (ChameleonBackgrounds.#globalStylesInjected) return;
|
|
259
|
+
ChameleonBackgrounds.#globalStylesInjected = true;
|
|
189
260
|
|
|
190
261
|
const css = `
|
|
191
|
-
|
|
262
|
+
.cbg-host {
|
|
192
263
|
position: relative;
|
|
193
264
|
}
|
|
194
|
-
|
|
195
|
-
|
|
265
|
+
body.cbg-host {
|
|
266
|
+
/* body naturally acts as relative for background sizing if not explicitly positioned differently */
|
|
267
|
+
}
|
|
268
|
+
.cbg-inner {
|
|
196
269
|
z-index: 2;
|
|
197
270
|
position: relative;
|
|
198
271
|
}
|
|
199
|
-
|
|
200
|
-
.cbg-loader-${uid} {
|
|
272
|
+
.cbg-loader {
|
|
201
273
|
height: 100%;
|
|
202
274
|
width: 100%;
|
|
203
|
-
position:
|
|
204
|
-
|
|
205
|
-
|
|
275
|
+
position: absolute;
|
|
276
|
+
top: 0;
|
|
277
|
+
left: 0;
|
|
278
|
+
background-image: var(--cbg-overlay-image, none);
|
|
279
|
+
background-color: var(--cbg-overlay-color, transparent);
|
|
206
280
|
opacity: 1;
|
|
207
281
|
z-index: 1;
|
|
208
|
-
transition: opacity
|
|
282
|
+
transition: opacity var(--cbg-duration, 2s) ease;
|
|
283
|
+
pointer-events: none;
|
|
284
|
+
}
|
|
285
|
+
body.cbg-host > .cbg-loader {
|
|
286
|
+
position: fixed;
|
|
287
|
+
}
|
|
288
|
+
.cbg-crossfade-el {
|
|
289
|
+
position: absolute;
|
|
209
290
|
top: 0;
|
|
210
291
|
left: 0;
|
|
292
|
+
height: 100%;
|
|
293
|
+
width: 100%;
|
|
294
|
+
background-size: cover;
|
|
295
|
+
background-position: center;
|
|
296
|
+
background-repeat: no-repeat;
|
|
297
|
+
z-index: 0;
|
|
298
|
+
opacity: 0;
|
|
299
|
+
transition: opacity var(--cbg-duration, 2s) ease;
|
|
300
|
+
pointer-events: none;
|
|
301
|
+
}
|
|
302
|
+
body.cbg-host > .cbg-crossfade-el {
|
|
303
|
+
position: fixed;
|
|
211
304
|
}
|
|
212
305
|
`;
|
|
213
306
|
|
|
214
307
|
const style = document.createElement('style');
|
|
215
|
-
style.
|
|
308
|
+
style.id = 'chameleon-global-styles';
|
|
216
309
|
style.textContent = css;
|
|
217
310
|
document.head.appendChild(style);
|
|
218
|
-
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Apply CSS custom properties to the host element.
|
|
315
|
+
*/
|
|
316
|
+
#updateCSSVariables() {
|
|
317
|
+
this.#element.classList.add(`cbg-host-${this.#uid}`, 'cbg-host');
|
|
318
|
+
this.#element.style.setProperty('--cbg-duration', `${this.#options.transitionDuration / 1000}s`);
|
|
319
|
+
this.#element.style.setProperty('--cbg-overlay-color', this.#options.overlayColor);
|
|
320
|
+
|
|
321
|
+
if (this.#options.overlayImage) {
|
|
322
|
+
this.#element.style.setProperty('--cbg-overlay-image', `url(${this.#options.overlayImage})`);
|
|
323
|
+
} else {
|
|
324
|
+
this.#element.style.setProperty('--cbg-overlay-image', 'none');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.#element.style.setProperty('--cbg-min-overlay', String(this.#options.minOverlay));
|
|
219
328
|
}
|
|
220
329
|
|
|
221
330
|
/**
|
|
@@ -226,6 +335,7 @@ class ChameleonBackgrounds {
|
|
|
226
335
|
|
|
227
336
|
const wrapper = document.createElement('div');
|
|
228
337
|
wrapper.id = `cbg-inner-${uid}`;
|
|
338
|
+
wrapper.classList.add('cbg-inner');
|
|
229
339
|
|
|
230
340
|
// Move all child nodes from the element into the wrapper
|
|
231
341
|
while (this.#element.firstChild) {
|
|
@@ -233,7 +343,7 @@ class ChameleonBackgrounds {
|
|
|
233
343
|
}
|
|
234
344
|
|
|
235
345
|
const loader = document.createElement('div');
|
|
236
|
-
loader.classList.add(`cbg-loader-${uid}`);
|
|
346
|
+
loader.classList.add('cbg-loader', `cbg-loader-${uid}`);
|
|
237
347
|
|
|
238
348
|
// Append wrapper and loader
|
|
239
349
|
this.#element.appendChild(wrapper);
|
|
@@ -251,7 +361,8 @@ class ChameleonBackgrounds {
|
|
|
251
361
|
if (this.#destroyed) return;
|
|
252
362
|
|
|
253
363
|
if (this.#options.type === 'single') {
|
|
254
|
-
|
|
364
|
+
const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;
|
|
365
|
+
this.#loadBackground(singleSrc);
|
|
255
366
|
} else if (this.#options.type === 'slider') {
|
|
256
367
|
this.#startSlider();
|
|
257
368
|
}
|
|
@@ -259,36 +370,87 @@ class ChameleonBackgrounds {
|
|
|
259
370
|
|
|
260
371
|
/**
|
|
261
372
|
* Preload an image and apply it as the element's background.
|
|
262
|
-
*
|
|
373
|
+
* Handles both string URLs and responsive image objects ({ url, srcset, sizes }).
|
|
374
|
+
* @param {string|ChameleonImageConfig} srcConfig - Image configuration or URL.
|
|
263
375
|
* @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).
|
|
376
|
+
* @param {boolean} [isCrossfade=false] - If true, handles true crossfading without solid color drop.
|
|
264
377
|
* @returns {Promise<void>}
|
|
265
378
|
*/
|
|
266
|
-
#loadBackground(
|
|
379
|
+
#loadBackground(srcConfig, isFirst = true, isCrossfade = false) {
|
|
267
380
|
if (this.#destroyed) return Promise.resolve();
|
|
268
381
|
|
|
269
382
|
return new Promise((resolve) => {
|
|
270
383
|
const img = new Image();
|
|
271
384
|
|
|
385
|
+
let urlStr = srcConfig;
|
|
386
|
+
if (typeof srcConfig === 'object' && srcConfig.url) {
|
|
387
|
+
urlStr = srcConfig.url;
|
|
388
|
+
if (srcConfig.srcset) img.srcset = srcConfig.srcset;
|
|
389
|
+
if (srcConfig.sizes) img.sizes = srcConfig.sizes;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
393
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Append to DOM to ensure viewport-based `sizes` calculate correctly in all browsers
|
|
397
|
+
img.style.position = 'absolute';
|
|
398
|
+
img.style.visibility = 'hidden';
|
|
399
|
+
img.style.width = '0';
|
|
400
|
+
img.style.height = '0';
|
|
401
|
+
this.#element.appendChild(img);
|
|
402
|
+
|
|
272
403
|
img.onload = () => {
|
|
404
|
+
img.remove();
|
|
273
405
|
if (this.#destroyed) return resolve();
|
|
274
406
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
407
|
+
// `img.currentSrc` retrieves the actual srcset chosen file, otherwise fallback to `urlStr`
|
|
408
|
+
const finalUrl = img.currentSrc || urlStr;
|
|
409
|
+
|
|
410
|
+
if (isCrossfade && !isFirst) {
|
|
411
|
+
// --- Crossfade Transition Mode ---
|
|
412
|
+
const crossfadeDiv = document.createElement('div');
|
|
413
|
+
crossfadeDiv.classList.add('cbg-crossfade-el', `cbg-crossfade-${this.#uid}`);
|
|
414
|
+
crossfadeDiv.style.backgroundImage = `url("${finalUrl}")`;
|
|
415
|
+
|
|
416
|
+
// Insert right behind the loader (or directly at start of host)
|
|
417
|
+
this.#element.insertBefore(crossfadeDiv, this.#element.firstChild);
|
|
418
|
+
|
|
419
|
+
// Trigger layout so the transition works
|
|
420
|
+
void crossfadeDiv.offsetWidth;
|
|
421
|
+
|
|
422
|
+
crossfadeDiv.style.opacity = '1';
|
|
423
|
+
|
|
424
|
+
setTimeout(() => {
|
|
425
|
+
if (this.#destroyed) return resolve();
|
|
426
|
+
this.#element.style.backgroundImage = `url("${finalUrl}")`;
|
|
427
|
+
this.#element.style.backgroundSize = 'cover';
|
|
428
|
+
this.#element.style.backgroundRepeat = 'no-repeat';
|
|
429
|
+
crossfadeDiv.remove();
|
|
430
|
+
resolve();
|
|
431
|
+
}, this.#options.transitionDuration);
|
|
432
|
+
|
|
433
|
+
} else {
|
|
434
|
+
// --- Solid Transition Mode or First Load ---
|
|
435
|
+
this.#element.style.backgroundImage = `url("${finalUrl}")`;
|
|
436
|
+
this.#element.style.backgroundSize = 'cover';
|
|
437
|
+
this.#element.style.backgroundRepeat = 'no-repeat';
|
|
438
|
+
|
|
439
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
440
|
+
if (loader) {
|
|
441
|
+
loader.style.opacity = String(this.#options.minOverlay);
|
|
442
|
+
}
|
|
443
|
+
resolve();
|
|
281
444
|
}
|
|
282
|
-
|
|
283
|
-
resolve();
|
|
284
445
|
};
|
|
285
446
|
|
|
286
447
|
img.onerror = () => {
|
|
287
|
-
|
|
288
|
-
|
|
448
|
+
img.remove();
|
|
449
|
+
console.warn(`[ChameleonBackgrounds] Failed to load image:`, srcConfig);
|
|
450
|
+
resolve(); // resolve anyway to keep logic flowing
|
|
289
451
|
};
|
|
290
452
|
|
|
291
|
-
img.src =
|
|
453
|
+
img.src = urlStr;
|
|
292
454
|
});
|
|
293
455
|
}
|
|
294
456
|
|
|
@@ -303,7 +465,7 @@ class ChameleonBackgrounds {
|
|
|
303
465
|
|
|
304
466
|
if (src !== undefined) {
|
|
305
467
|
if (this.#options.type === 'slider' && typeof src === 'string') {
|
|
306
|
-
// If the user passes a comma-separated string, split it.
|
|
468
|
+
// If the user passes a comma-separated string, split it.
|
|
307
469
|
this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];
|
|
308
470
|
} else {
|
|
309
471
|
this.#options.src = src;
|
|
@@ -316,23 +478,25 @@ class ChameleonBackgrounds {
|
|
|
316
478
|
this.#sliderIntervalId = null;
|
|
317
479
|
}
|
|
318
480
|
|
|
481
|
+
// For solid mode, we fade the loader up. For crossfade, we don't.
|
|
319
482
|
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
320
|
-
if (loader) {
|
|
483
|
+
if (loader && this.#options.transitionMode === 'solid') {
|
|
321
484
|
loader.style.opacity = '1';
|
|
322
485
|
}
|
|
323
486
|
|
|
324
487
|
return new Promise((resolve) => {
|
|
488
|
+
const waitTime = this.#options.transitionMode === 'solid' ? this.#options.transitionDuration : 0;
|
|
325
489
|
setTimeout(() => {
|
|
326
490
|
if (this.#destroyed) return resolve();
|
|
327
491
|
|
|
328
492
|
if (this.#options.type === 'single') {
|
|
329
|
-
const singleSrc =
|
|
330
|
-
this.#loadBackground(singleSrc, false).then(resolve);
|
|
493
|
+
const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;
|
|
494
|
+
this.#loadBackground(singleSrc, false, this.#options.transitionMode === 'crossfade').then(resolve);
|
|
331
495
|
} else if (this.#options.type === 'slider') {
|
|
332
496
|
this.#startSlider();
|
|
333
497
|
resolve();
|
|
334
498
|
}
|
|
335
|
-
},
|
|
499
|
+
}, waitTime);
|
|
336
500
|
});
|
|
337
501
|
}
|
|
338
502
|
|
|
@@ -346,14 +510,11 @@ class ChameleonBackgrounds {
|
|
|
346
510
|
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
347
511
|
this.#options = { ...this.#options, ...normalized };
|
|
348
512
|
|
|
349
|
-
|
|
350
|
-
if
|
|
351
|
-
|
|
352
|
-
this.#
|
|
513
|
+
this.#updateCSSVariables();
|
|
514
|
+
// Restart slider if needed
|
|
515
|
+
if (this.#options.type === 'slider' && !this.#isPaused && !this.#sliderIntervalId) {
|
|
516
|
+
this.#startSlider();
|
|
353
517
|
}
|
|
354
|
-
|
|
355
|
-
// Inject updated styles
|
|
356
|
-
this.#injectStyles();
|
|
357
518
|
}
|
|
358
519
|
|
|
359
520
|
// ---------------------------------------------------------------------------
|
|
@@ -373,52 +534,67 @@ class ChameleonBackgrounds {
|
|
|
373
534
|
}
|
|
374
535
|
|
|
375
536
|
// Load the first slide immediately
|
|
376
|
-
|
|
377
|
-
this.#loadBackground(sources[
|
|
537
|
+
this.#currentSlideIndex = 0;
|
|
538
|
+
this.#loadBackground(sources[this.#currentSlideIndex]).then(() => {
|
|
378
539
|
if (this.#destroyed) return;
|
|
379
540
|
|
|
380
|
-
|
|
541
|
+
this.#currentSlideIndex = 1;
|
|
381
542
|
if (sources.length === 1) return; // only one slide, nothing to rotate
|
|
382
543
|
|
|
383
|
-
|
|
544
|
+
this.#startSliderLoop();
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
#startSliderLoop() {
|
|
549
|
+
const sources = this.#options.src;
|
|
384
550
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
551
|
+
// Parse as integers to prevent string concatenation bugs if users passed strings
|
|
552
|
+
const sDuration = parseInt(this.#options.sliderDuration, 10) || 8000;
|
|
553
|
+
const tDuration = parseInt(this.#options.transitionDuration, 10) || 2000;
|
|
554
|
+
const interval = sDuration + tDuration * (this.#options.transitionMode === 'solid' ? 2 : 1);
|
|
390
555
|
|
|
391
|
-
|
|
392
|
-
|
|
556
|
+
this.#sliderIntervalId = setInterval(() => {
|
|
557
|
+
if (this.#destroyed || this.#isPaused) {
|
|
558
|
+
clearInterval(this.#sliderIntervalId);
|
|
559
|
+
this.#sliderIntervalId = null;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
393
562
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
563
|
+
this.#cycleSliderSlide(sources[this.#currentSlideIndex]);
|
|
564
|
+
this.#currentSlideIndex++;
|
|
565
|
+
|
|
566
|
+
if (this.#currentSlideIndex >= sources.length) {
|
|
567
|
+
if (this.#options.sliderLoop) {
|
|
568
|
+
this.#currentSlideIndex = 0;
|
|
569
|
+
} else {
|
|
570
|
+
clearInterval(this.#sliderIntervalId);
|
|
571
|
+
this.#sliderIntervalId = null;
|
|
401
572
|
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
573
|
+
}
|
|
574
|
+
}, interval);
|
|
404
575
|
}
|
|
405
576
|
|
|
406
577
|
/**
|
|
407
578
|
* Cycle to a specific slide without resetting the slider instance.
|
|
408
|
-
* @param {string} src - The image URL to load.
|
|
579
|
+
* @param {string|ChameleonImageConfig} src - The image URL/config to load.
|
|
409
580
|
*/
|
|
410
581
|
#cycleSliderSlide(src) {
|
|
411
582
|
if (this.#destroyed) return;
|
|
412
583
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
loader
|
|
416
|
-
|
|
584
|
+
if (this.#options.transitionMode === 'solid') {
|
|
585
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
586
|
+
if (loader) {
|
|
587
|
+
loader.style.opacity = '1';
|
|
588
|
+
}
|
|
417
589
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
590
|
+
setTimeout(() => {
|
|
591
|
+
if (this.#destroyed) return;
|
|
592
|
+
this.#loadBackground(src, false, false);
|
|
593
|
+
}, this.#options.transitionDuration);
|
|
594
|
+
} else {
|
|
595
|
+
// Crossfade mode triggers immediately
|
|
596
|
+
this.#loadBackground(src, false, true);
|
|
597
|
+
}
|
|
422
598
|
}
|
|
423
599
|
|
|
424
600
|
// ---------------------------------------------------------------------------
|