chameleon-backgrounds 2.1.3 → 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 +55 -11
- package/dist/chameleon-backgrounds.js +653 -467
- 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 +268 -98
|
@@ -1,557 +1,743 @@
|
|
|
1
1
|
(function webpackUniversalModuleDefinition(root, factory) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
if (typeof exports === 'object' && typeof module === 'object')
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
else if (typeof define === 'function' && define.amd)
|
|
5
|
+
define([], factory);
|
|
6
|
+
else if (typeof exports === 'object')
|
|
7
|
+
exports["ChameleonBackgrounds"] = factory();
|
|
8
|
+
else
|
|
9
|
+
root["ChameleonBackgrounds"] = factory();
|
|
10
10
|
})(this, () => {
|
|
11
|
-
return /******/ (() => { // webpackBootstrap
|
|
11
|
+
return /******/ (() => { // webpackBootstrap
|
|
12
12
|
/******/ "use strict";
|
|
13
13
|
/******/ // The require scope
|
|
14
14
|
/******/ const __webpack_require__ = {};
|
|
15
|
-
/******/
|
|
15
|
+
/******/
|
|
16
16
|
/************************************************************************/
|
|
17
17
|
/******/ /* webpack/runtime/define property getters */
|
|
18
18
|
/******/ (() => {
|
|
19
19
|
/******/ // define getter/value functions for harmony exports
|
|
20
20
|
/******/ __webpack_require__.d = (exports, definition) => {
|
|
21
|
-
/******/ if(Array.isArray(definition)) {
|
|
21
|
+
/******/ if (Array.isArray(definition)) {
|
|
22
22
|
/******/ var i = 0;
|
|
23
|
-
/******/ while(i < definition.length) {
|
|
23
|
+
/******/ while (i < definition.length) {
|
|
24
24
|
/******/ var key = definition[i++];
|
|
25
25
|
/******/ var binding = definition[i++];
|
|
26
|
-
/******/ if(!__webpack_require__.o(exports, key)) {
|
|
27
|
-
/******/ if(binding === 0) {
|
|
26
|
+
/******/ if (!__webpack_require__.o(exports, key)) {
|
|
27
|
+
/******/ if (binding === 0) {
|
|
28
28
|
/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] });
|
|
29
|
-
/******/
|
|
29
|
+
/******/
|
|
30
|
+
} else {
|
|
30
31
|
/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding });
|
|
31
|
-
/******/
|
|
32
|
-
|
|
33
|
-
/******/
|
|
34
|
-
|
|
35
|
-
/******/
|
|
36
|
-
|
|
32
|
+
/******/
|
|
33
|
+
}
|
|
34
|
+
/******/
|
|
35
|
+
} else if (binding === 0) { i++; }
|
|
36
|
+
/******/
|
|
37
|
+
}
|
|
38
|
+
/******/
|
|
39
|
+
} else {
|
|
40
|
+
/******/ for (var key in definition) {
|
|
41
|
+
/******/ if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
37
42
|
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
38
|
-
/******/
|
|
39
|
-
|
|
40
|
-
/******/
|
|
41
|
-
|
|
42
|
-
/******/
|
|
43
|
-
|
|
43
|
+
/******/
|
|
44
|
+
}
|
|
45
|
+
/******/
|
|
46
|
+
}
|
|
47
|
+
/******/
|
|
48
|
+
}
|
|
49
|
+
/******/
|
|
50
|
+
};
|
|
51
|
+
/******/
|
|
52
|
+
})();
|
|
53
|
+
/******/
|
|
44
54
|
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
45
55
|
/******/ (() => {
|
|
46
56
|
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
47
|
-
/******/
|
|
48
|
-
|
|
57
|
+
/******/
|
|
58
|
+
})();
|
|
59
|
+
/******/
|
|
49
60
|
/******/ /* webpack/runtime/make namespace object */
|
|
50
61
|
/******/ (() => {
|
|
51
62
|
/******/ // define __esModule on exports
|
|
52
63
|
/******/ __webpack_require__.r = (exports) => {
|
|
53
|
-
/******/ if(Symbol.toStringTag) {
|
|
64
|
+
/******/ if (Symbol.toStringTag) {
|
|
54
65
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
55
|
-
/******/
|
|
66
|
+
/******/
|
|
67
|
+
}
|
|
56
68
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
57
|
-
/******/
|
|
58
|
-
|
|
59
|
-
/******/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
/******/
|
|
70
|
+
};
|
|
71
|
+
/******/
|
|
72
|
+
})();
|
|
73
|
+
/******/
|
|
74
|
+
/************************************************************************/
|
|
75
|
+
let __webpack_exports__ = {};
|
|
76
|
+
/*!**************************************!*\
|
|
77
|
+
!*** ./src/chameleon-backgrounds.js ***!
|
|
78
|
+
\**************************************/
|
|
79
|
+
__webpack_require__.r(__webpack_exports__);
|
|
66
80
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
67
81
|
/* harmony export */ ChameleonBackgrounds: () => (/* binding */ ChameleonBackgrounds),
|
|
68
82
|
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
69
|
-
/* harmony export */
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
83
|
+
/* harmony export */
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* _____ _ _
|
|
87
|
+
* / ____| | | |
|
|
88
|
+
* | | | |__ __ _ _ __ ___ ___| | ___ ___ _ __
|
|
89
|
+
* | | | '_ \ / _` | '_ ` _ \ / _ \ |/ _ \/ _ \| '_ \
|
|
90
|
+
* | |____| | | | (_| | | | | | | __/ | __/ (_) | | | |
|
|
91
|
+
* \_____|_| |_|\__,_|_| |_| |_|\___|_|\___|\___|_| |_|_
|
|
92
|
+
* | _ \ | | | |
|
|
93
|
+
* | |_) | __ _ ___| | ____ _ _ __ ___ _ _ _ __ __| |___
|
|
94
|
+
* | _ < / _` |/ __| |/ / _` | '__/ _ \| | | | '_ \ / _` / __|
|
|
95
|
+
* | |_) | (_| | (__| < (_| | | | (_) | |_| | | | | (_| \__ \
|
|
96
|
+
* |____/ \__,_|\___|_|\_\__, |_| \___/ \__,_|_| |_|\__,_|___/
|
|
97
|
+
* __/ |
|
|
98
|
+
* |___/
|
|
99
|
+
*
|
|
100
|
+
* @module ChameleonBackgrounds
|
|
101
|
+
* @version 3.0.0
|
|
102
|
+
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
103
|
+
* @license MIT
|
|
104
|
+
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
105
|
+
*
|
|
106
|
+
* A zero-dependency JavaScript library to dynamically load background
|
|
107
|
+
* images with elegant fade-in transitions and slideshow support.
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @typedef {Object} ChameleonImageConfig
|
|
112
|
+
* @property {string} url - The fallback URL.
|
|
113
|
+
* @property {string} [srcset] - Optional srcset.
|
|
114
|
+
* @property {string} [sizes] - Optional sizes.
|
|
115
|
+
*
|
|
116
|
+
* @typedef {Object} ChameleonOptions
|
|
117
|
+
* @property {string|HTMLElement} element - CSS selector or DOM element to attach to.
|
|
118
|
+
* @property {'single'|'slider'} type - Background mode.
|
|
119
|
+
* @property {string|string[]|ChameleonImageConfig|ChameleonImageConfig[]} src - Image URL(s) or config object(s).
|
|
120
|
+
* @property {string} overlayColor - Overlay color (hex, rgb, rgba, hsl).
|
|
121
|
+
* @property {string} [overlayImage] - Optional overlay pattern image URL.
|
|
122
|
+
* @property {number} [minOverlay=0] - Minimum overlay opacity after fade (0–1).
|
|
123
|
+
* @property {number} [transitionDuration=2000] - Fade duration in milliseconds.
|
|
124
|
+
* @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.
|
|
125
|
+
* @property {boolean} [sliderLoop=false] - Whether the slider restarts after the last slide.
|
|
126
|
+
* @property {'high'|'low'|'auto'} [fetchPriority='auto'] - fetchPriority for the first loaded image (e.g., 'high' for LCP images).
|
|
127
|
+
* @property {boolean} [lazyLoad=true] - Whether to wait until element is in viewport using IntersectionObserver.
|
|
128
|
+
* @property {'solid'|'crossfade'} [transitionMode='solid'] - Transition mode: 'solid' (fade to color) or 'crossfade' (fade between images).
|
|
129
|
+
*/
|
|
130
|
+
|
|
131
|
+
class ChameleonBackgrounds {
|
|
132
|
+
/** @type {ChameleonOptions} */
|
|
133
|
+
static #DEFAULTS = Object.freeze({
|
|
134
|
+
element: 'body',
|
|
135
|
+
type: 'single',
|
|
136
|
+
src: '',
|
|
137
|
+
overlayColor: '#0f1e25',
|
|
138
|
+
overlayImage: null,
|
|
139
|
+
minOverlay: 0,
|
|
140
|
+
transitionDuration: 2000,
|
|
141
|
+
sliderDuration: 8000,
|
|
142
|
+
sliderLoop: false,
|
|
143
|
+
fetchPriority: 'auto',
|
|
144
|
+
lazyLoad: true,
|
|
145
|
+
transitionMode: 'solid',
|
|
146
|
+
respectReducedMotion: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Legacy snake_case → camelCase alias map
|
|
150
|
+
static #ALIASES = Object.freeze({
|
|
151
|
+
transition_duration: 'transitionDuration',
|
|
152
|
+
slider_duration: 'sliderDuration',
|
|
153
|
+
slider_loop: 'sliderLoop',
|
|
154
|
+
min_overlay: 'minOverlay',
|
|
155
|
+
overlay_color: 'overlayColor',
|
|
156
|
+
overlay_image: 'overlayImage',
|
|
157
|
+
fetch_priority: 'fetchPriority',
|
|
158
|
+
lazy_load: 'lazyLoad',
|
|
159
|
+
transition_mode: 'transitionMode',
|
|
160
|
+
respect_reduced_motion: 'respectReducedMotion',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/** @type {boolean} */
|
|
164
|
+
static #globalStylesInjected = false;
|
|
165
|
+
|
|
166
|
+
/** @type {ChameleonOptions} */
|
|
167
|
+
#options;
|
|
168
|
+
|
|
169
|
+
/** @type {HTMLElement} */
|
|
170
|
+
#element;
|
|
171
|
+
|
|
172
|
+
/** @type {string} */
|
|
173
|
+
#uid;
|
|
174
|
+
|
|
175
|
+
/** @type {string} */
|
|
176
|
+
#originalHTML;
|
|
177
|
+
|
|
178
|
+
/** @type {number|null} */
|
|
179
|
+
#sliderIntervalId = null;
|
|
180
|
+
|
|
181
|
+
/** @type {number} */
|
|
182
|
+
#currentSlideIndex = 0;
|
|
183
|
+
|
|
184
|
+
/** @type {boolean} */
|
|
185
|
+
#isPaused = false;
|
|
186
|
+
|
|
187
|
+
/** @type {IntersectionObserver|null} */
|
|
188
|
+
#observer = null;
|
|
189
|
+
|
|
190
|
+
/** @type {boolean} */
|
|
191
|
+
#destroyed = false;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a new ChameleonBackgrounds instance.
|
|
195
|
+
* @param {Partial<ChameleonOptions>} [options]
|
|
196
|
+
*/
|
|
197
|
+
constructor(options = {}) {
|
|
198
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(options);
|
|
199
|
+
this.#options = { ...ChameleonBackgrounds.#DEFAULTS, ...normalized };
|
|
200
|
+
this.#uid = ChameleonBackgrounds.#generateUID();
|
|
201
|
+
this.#element = ChameleonBackgrounds.#resolveElement(this.#options.element);
|
|
202
|
+
this.#originalHTML = this.#element.innerHTML;
|
|
203
|
+
|
|
204
|
+
this.#init();
|
|
205
|
+
}
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Public API
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clean up all injected DOM, styles, and intervals.
|
|
213
|
+
* Restores the target element to its original state.
|
|
214
|
+
*/
|
|
215
|
+
destroy() {
|
|
216
|
+
if (this.#destroyed) return;
|
|
217
|
+
this.#destroyed = true;
|
|
193
218
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
// Stop any running slider
|
|
220
|
+
if (this.#sliderIntervalId !== null) {
|
|
221
|
+
clearInterval(this.#sliderIntervalId);
|
|
222
|
+
this.#sliderIntervalId = null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.#observer) {
|
|
226
|
+
this.#observer.disconnect();
|
|
227
|
+
this.#observer = null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Clean up dynamic classes on host
|
|
231
|
+
this.#element.classList.remove(`cbg-host-${this.#uid}`, 'cbg-host');
|
|
232
|
+
|
|
233
|
+
// Unwrap the original content instead of resetting innerHTML
|
|
234
|
+
const wrapper = this.#element.querySelector(`#cbg-inner-${this.#uid}`);
|
|
235
|
+
if (wrapper && wrapper.parentNode === this.#element) {
|
|
236
|
+
while (wrapper.firstChild) {
|
|
237
|
+
this.#element.insertBefore(wrapper.firstChild, wrapper);
|
|
238
|
+
}
|
|
239
|
+
this.#element.removeChild(wrapper);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Remove loader
|
|
243
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
244
|
+
if (loader) {
|
|
245
|
+
loader.remove();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Remove temporary crossfade elements
|
|
249
|
+
const crossfadeElements = this.#element.querySelectorAll(`.cbg-crossfade-${this.#uid}`);
|
|
250
|
+
crossfadeElements.forEach(el => el.remove());
|
|
251
|
+
|
|
252
|
+
// Remove inline CSS Variables and background-image
|
|
253
|
+
this.#element.style.backgroundImage = '';
|
|
254
|
+
this.#element.style.removeProperty('--cbg-duration');
|
|
255
|
+
this.#element.style.removeProperty('--cbg-overlay-color');
|
|
256
|
+
this.#element.style.removeProperty('--cbg-overlay-image');
|
|
257
|
+
this.#element.style.removeProperty('--cbg-min-overlay');
|
|
199
258
|
}
|
|
200
|
-
this.#element.removeChild(wrapper);
|
|
201
|
-
}
|
|
202
259
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
260
|
+
/**
|
|
261
|
+
* The resolved options (read-only copy).
|
|
262
|
+
* @returns {ChameleonOptions}
|
|
263
|
+
*/
|
|
264
|
+
get options() {
|
|
265
|
+
return { ...this.#options };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Initialization
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
#init() {
|
|
273
|
+
this.#injectGlobalStyles();
|
|
274
|
+
this.#updateCSSVariables();
|
|
275
|
+
this.#buildDOM();
|
|
208
276
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (document.readyState === 'complete') {
|
|
237
|
-
this.#retrieveBackground();
|
|
238
|
-
} else {
|
|
239
|
-
window.addEventListener('load', () => this.#retrieveBackground(), { once: true });
|
|
277
|
+
// Check for prefers-reduced-motion to auto-pause slider if enabled
|
|
278
|
+
if (this.#options.respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
279
|
+
this.#isPaused = true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (this.#options.lazyLoad && 'IntersectionObserver' in window) {
|
|
283
|
+
this.#observer = new IntersectionObserver((entries) => {
|
|
284
|
+
entries.forEach((entry) => {
|
|
285
|
+
if (entry.isIntersecting) {
|
|
286
|
+
this.#observer.disconnect();
|
|
287
|
+
this.#observer = null;
|
|
288
|
+
this.#retrieveBackground();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
this.#observer.observe(this.#element);
|
|
293
|
+
} else {
|
|
294
|
+
if (this.#element.matches('body')) {
|
|
295
|
+
queueMicrotask(() => this.#retrieveBackground());
|
|
296
|
+
} else {
|
|
297
|
+
if (document.readyState === 'complete') {
|
|
298
|
+
this.#retrieveBackground();
|
|
299
|
+
} else {
|
|
300
|
+
window.addEventListener('load', () => this.#retrieveBackground(), { once: true });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
240
304
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
305
|
+
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// Play / Pause API
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Pause the slideshow.
|
|
312
|
+
*/
|
|
313
|
+
pause() {
|
|
314
|
+
if (this.#destroyed || this.#options.type !== 'slider') return;
|
|
315
|
+
this.#isPaused = true;
|
|
316
|
+
if (this.#sliderIntervalId !== null) {
|
|
317
|
+
clearInterval(this.#sliderIntervalId);
|
|
318
|
+
this.#sliderIntervalId = null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Resume the slideshow.
|
|
324
|
+
*/
|
|
325
|
+
play() {
|
|
326
|
+
if (this.#destroyed || this.#options.type !== 'slider') return;
|
|
327
|
+
this.#isPaused = false;
|
|
328
|
+
// Only restart if not currently running
|
|
329
|
+
if (this.#sliderIntervalId === null) {
|
|
330
|
+
this.#startSliderLoop();
|
|
331
|
+
}
|
|
265
332
|
}
|
|
266
333
|
|
|
267
|
-
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// DOM Construction
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Inject global styles once.
|
|
340
|
+
*/
|
|
341
|
+
#injectGlobalStyles() {
|
|
342
|
+
if (ChameleonBackgrounds.#globalStylesInjected) return;
|
|
343
|
+
ChameleonBackgrounds.#globalStylesInjected = true;
|
|
344
|
+
|
|
345
|
+
const css = `
|
|
346
|
+
.cbg-host {
|
|
347
|
+
position: relative;
|
|
348
|
+
}
|
|
349
|
+
body.cbg-host {
|
|
350
|
+
/* body naturally acts as relative for background sizing if not explicitly positioned differently */
|
|
351
|
+
}
|
|
352
|
+
.cbg-inner {
|
|
268
353
|
z-index: 2;
|
|
269
354
|
position: relative;
|
|
270
355
|
}
|
|
271
|
-
|
|
272
|
-
.cbg-loader-${uid} {
|
|
356
|
+
.cbg-loader {
|
|
273
357
|
height: 100%;
|
|
274
358
|
width: 100%;
|
|
275
|
-
position:
|
|
276
|
-
|
|
277
|
-
|
|
359
|
+
position: absolute;
|
|
360
|
+
top: 0;
|
|
361
|
+
left: 0;
|
|
362
|
+
background-image: var(--cbg-overlay-image, none);
|
|
363
|
+
background-color: var(--cbg-overlay-color, transparent);
|
|
278
364
|
opacity: 1;
|
|
279
365
|
z-index: 1;
|
|
280
|
-
transition: opacity
|
|
366
|
+
transition: opacity var(--cbg-duration, 2s) ease;
|
|
367
|
+
pointer-events: none;
|
|
368
|
+
}
|
|
369
|
+
body.cbg-host > .cbg-loader {
|
|
370
|
+
position: fixed;
|
|
371
|
+
}
|
|
372
|
+
.cbg-crossfade-el {
|
|
373
|
+
position: absolute;
|
|
281
374
|
top: 0;
|
|
282
375
|
left: 0;
|
|
376
|
+
height: 100%;
|
|
377
|
+
width: 100%;
|
|
378
|
+
background-size: cover;
|
|
379
|
+
background-position: center;
|
|
380
|
+
background-repeat: no-repeat;
|
|
381
|
+
z-index: 0;
|
|
382
|
+
opacity: 0;
|
|
383
|
+
transition: opacity var(--cbg-duration, 2s) ease;
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
}
|
|
386
|
+
body.cbg-host > .cbg-crossfade-el {
|
|
387
|
+
position: fixed;
|
|
283
388
|
}
|
|
284
389
|
`;
|
|
285
390
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Wrap existing content and insert the overlay loader.
|
|
295
|
-
*/
|
|
296
|
-
#buildDOM() {
|
|
297
|
-
const uid = this.#uid;
|
|
298
|
-
|
|
299
|
-
const wrapper = document.createElement('div');
|
|
300
|
-
wrapper.id = `cbg-inner-${uid}`;
|
|
301
|
-
|
|
302
|
-
// Move all child nodes from the element into the wrapper
|
|
303
|
-
while (this.#element.firstChild) {
|
|
304
|
-
wrapper.appendChild(this.#element.firstChild);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const loader = document.createElement('div');
|
|
308
|
-
loader.classList.add(`cbg-loader-${uid}`);
|
|
309
|
-
|
|
310
|
-
// Append wrapper and loader
|
|
311
|
-
this.#element.appendChild(wrapper);
|
|
312
|
-
this.#element.appendChild(loader);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
// Background Loading
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
391
|
+
const style = document.createElement('style');
|
|
392
|
+
style.id = 'chameleon-global-styles';
|
|
393
|
+
style.textContent = css;
|
|
394
|
+
document.head.appendChild(style);
|
|
395
|
+
}
|
|
318
396
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Apply CSS custom properties to the host element.
|
|
399
|
+
*/
|
|
400
|
+
#updateCSSVariables() {
|
|
401
|
+
this.#element.classList.add(`cbg-host-${this.#uid}`, 'cbg-host');
|
|
402
|
+
this.#element.style.setProperty('--cbg-duration', `${this.#options.transitionDuration / 1000}s`);
|
|
403
|
+
this.#element.style.setProperty('--cbg-overlay-color', this.#options.overlayColor);
|
|
404
|
+
|
|
405
|
+
if (this.#options.overlayImage) {
|
|
406
|
+
this.#element.style.setProperty('--cbg-overlay-image', `url(${this.#options.overlayImage})`);
|
|
407
|
+
} else {
|
|
408
|
+
this.#element.style.setProperty('--cbg-overlay-image', 'none');
|
|
409
|
+
}
|
|
324
410
|
|
|
325
|
-
|
|
326
|
-
this.#loadBackground(typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0]);
|
|
327
|
-
} else if (this.#options.type === 'slider') {
|
|
328
|
-
this.#startSlider();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Preload an image and apply it as the element's background.
|
|
334
|
-
* @param {string} src - Image URL to load.
|
|
335
|
-
* @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).
|
|
336
|
-
* @returns {Promise<void>}
|
|
337
|
-
*/
|
|
338
|
-
#loadBackground(src, isFirst = true) {
|
|
339
|
-
if (this.#destroyed) return Promise.resolve();
|
|
340
|
-
|
|
341
|
-
return new Promise((resolve) => {
|
|
342
|
-
const img = new Image();
|
|
343
|
-
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
344
|
-
img.fetchPriority = this.#options.fetchPriority;
|
|
411
|
+
this.#element.style.setProperty('--cbg-min-overlay', String(this.#options.minOverlay));
|
|
345
412
|
}
|
|
346
413
|
|
|
347
|
-
|
|
348
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Wrap existing content and insert the overlay loader.
|
|
416
|
+
*/
|
|
417
|
+
#buildDOM() {
|
|
418
|
+
const uid = this.#uid;
|
|
349
419
|
|
|
350
|
-
|
|
351
|
-
|
|
420
|
+
const wrapper = document.createElement('div');
|
|
421
|
+
wrapper.id = `cbg-inner-${uid}`;
|
|
422
|
+
wrapper.classList.add('cbg-inner');
|
|
352
423
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
424
|
+
// Move all child nodes from the element into the wrapper
|
|
425
|
+
while (this.#element.firstChild) {
|
|
426
|
+
wrapper.appendChild(this.#element.firstChild);
|
|
356
427
|
}
|
|
357
428
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
img.onerror = () => {
|
|
362
|
-
console.warn(`[ChameleonBackgrounds] Failed to load image: ${src}`);
|
|
363
|
-
resolve();
|
|
364
|
-
};
|
|
429
|
+
const loader = document.createElement('div');
|
|
430
|
+
loader.classList.add('cbg-loader', `cbg-loader-${uid}`);
|
|
365
431
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
/**
|
|
372
|
-
* Reload background with a fade-out → swap → fade-in cycle.
|
|
373
|
-
* @param {string} [src] - Optional new image URL. If omitted, reloads using current options.
|
|
374
|
-
* @returns {Promise<void>}
|
|
375
|
-
*/
|
|
376
|
-
reloadBackground(src) {
|
|
377
|
-
if (this.#destroyed) return Promise.resolve();
|
|
378
|
-
|
|
379
|
-
if (src !== undefined) {
|
|
380
|
-
if (this.#options.type === 'slider' && typeof src === 'string') {
|
|
381
|
-
// If the user passes a comma-separated string, split it. Otherwise wrap it.
|
|
382
|
-
this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];
|
|
383
|
-
} else {
|
|
384
|
-
this.#options.src = src;
|
|
432
|
+
// Append wrapper and loader
|
|
433
|
+
this.#element.appendChild(wrapper);
|
|
434
|
+
this.#element.appendChild(loader);
|
|
385
435
|
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Stop any running slider
|
|
389
|
-
if (this.#sliderIntervalId !== null) {
|
|
390
|
-
clearInterval(this.#sliderIntervalId);
|
|
391
|
-
this.#sliderIntervalId = null;
|
|
392
|
-
}
|
|
393
436
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
437
|
+
// ---------------------------------------------------------------------------
|
|
438
|
+
// Background Loading
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
398
440
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
441
|
+
/**
|
|
442
|
+
* Route to the correct loader based on type.
|
|
443
|
+
*/
|
|
444
|
+
#retrieveBackground() {
|
|
445
|
+
if (this.#destroyed) return;
|
|
402
446
|
|
|
403
447
|
if (this.#options.type === 'single') {
|
|
404
|
-
const singleSrc =
|
|
405
|
-
this.#loadBackground(singleSrc
|
|
448
|
+
const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;
|
|
449
|
+
this.#loadBackground(singleSrc);
|
|
406
450
|
} else if (this.#options.type === 'slider') {
|
|
407
451
|
this.#startSlider();
|
|
408
|
-
resolve();
|
|
409
452
|
}
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Update options on the fly without destroying the instance.
|
|
416
|
-
* @param {Object} newOptions - The new options to merge.
|
|
417
|
-
*/
|
|
418
|
-
reloadOptions(newOptions) {
|
|
419
|
-
if (this.#destroyed) return;
|
|
420
|
-
|
|
421
|
-
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
422
|
-
this.#options = { ...this.#options, ...normalized };
|
|
423
|
-
|
|
424
|
-
// Remove old styles
|
|
425
|
-
if (this.#styleElement?.parentNode) {
|
|
426
|
-
this.#styleElement.parentNode.removeChild(this.#styleElement);
|
|
427
|
-
this.#styleElement = null;
|
|
428
|
-
}
|
|
453
|
+
}
|
|
429
454
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
455
|
+
/**
|
|
456
|
+
* Preload an image and apply it as the element's background.
|
|
457
|
+
* Handles both string URLs and responsive image objects ({ url, srcset, sizes }).
|
|
458
|
+
* @param {string|ChameleonImageConfig} srcConfig - Image configuration or URL.
|
|
459
|
+
* @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).
|
|
460
|
+
* @param {boolean} [isCrossfade=false] - If true, handles true crossfading without solid color drop.
|
|
461
|
+
* @returns {Promise<void>}
|
|
462
|
+
*/
|
|
463
|
+
#loadBackground(srcConfig, isFirst = true, isCrossfade = false) {
|
|
464
|
+
if (this.#destroyed) return Promise.resolve();
|
|
465
|
+
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
const img = new Image();
|
|
468
|
+
|
|
469
|
+
let urlStr = srcConfig;
|
|
470
|
+
if (typeof srcConfig === 'object' && srcConfig.url) {
|
|
471
|
+
urlStr = srcConfig.url;
|
|
472
|
+
if (srcConfig.srcset) img.srcset = srcConfig.srcset;
|
|
473
|
+
if (srcConfig.sizes) img.sizes = srcConfig.sizes;
|
|
474
|
+
}
|
|
433
475
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
476
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
477
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
478
|
+
}
|
|
437
479
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
480
|
+
// Append to DOM to ensure viewport-based `sizes` calculate correctly in all browsers
|
|
481
|
+
img.style.position = 'absolute';
|
|
482
|
+
img.style.visibility = 'hidden';
|
|
483
|
+
img.style.width = '0';
|
|
484
|
+
img.style.height = '0';
|
|
485
|
+
this.#element.appendChild(img);
|
|
486
|
+
|
|
487
|
+
img.onload = () => {
|
|
488
|
+
img.remove();
|
|
489
|
+
if (this.#destroyed) return resolve();
|
|
490
|
+
|
|
491
|
+
// `img.currentSrc` retrieves the actual srcset chosen file, otherwise fallback to `urlStr`
|
|
492
|
+
const finalUrl = img.currentSrc || urlStr;
|
|
493
|
+
|
|
494
|
+
if (isCrossfade && !isFirst) {
|
|
495
|
+
// --- Crossfade Transition Mode ---
|
|
496
|
+
const crossfadeDiv = document.createElement('div');
|
|
497
|
+
crossfadeDiv.classList.add('cbg-crossfade-el', `cbg-crossfade-${this.#uid}`);
|
|
498
|
+
crossfadeDiv.style.backgroundImage = `url("${finalUrl}")`;
|
|
499
|
+
|
|
500
|
+
// Insert right behind the loader (or directly at start of host)
|
|
501
|
+
this.#element.insertBefore(crossfadeDiv, this.#element.firstChild);
|
|
502
|
+
|
|
503
|
+
// Trigger layout so the transition works
|
|
504
|
+
void crossfadeDiv.offsetWidth;
|
|
505
|
+
|
|
506
|
+
crossfadeDiv.style.opacity = '1';
|
|
507
|
+
|
|
508
|
+
setTimeout(() => {
|
|
509
|
+
if (this.#destroyed) return resolve();
|
|
510
|
+
this.#element.style.backgroundImage = `url("${finalUrl}")`;
|
|
511
|
+
this.#element.style.backgroundSize = 'cover';
|
|
512
|
+
this.#element.style.backgroundRepeat = 'no-repeat';
|
|
513
|
+
crossfadeDiv.remove();
|
|
514
|
+
resolve();
|
|
515
|
+
}, this.#options.transitionDuration);
|
|
516
|
+
|
|
517
|
+
} else {
|
|
518
|
+
// --- Solid Transition Mode or First Load ---
|
|
519
|
+
this.#element.style.backgroundImage = `url("${finalUrl}")`;
|
|
520
|
+
this.#element.style.backgroundSize = 'cover';
|
|
521
|
+
this.#element.style.backgroundRepeat = 'no-repeat';
|
|
522
|
+
|
|
523
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
524
|
+
if (loader) {
|
|
525
|
+
loader.style.opacity = String(this.#options.minOverlay);
|
|
526
|
+
}
|
|
527
|
+
resolve();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
img.onerror = () => {
|
|
532
|
+
img.remove();
|
|
533
|
+
console.warn(`[ChameleonBackgrounds] Failed to load image:`, srcConfig);
|
|
534
|
+
resolve(); // resolve anyway to keep logic flowing
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
img.src = urlStr;
|
|
538
|
+
});
|
|
539
|
+
}
|
|
443
540
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
541
|
+
/**
|
|
542
|
+
/**
|
|
543
|
+
* Reload background with a fade-out → swap → fade-in cycle.
|
|
544
|
+
* @param {string} [src] - Optional new image URL. If omitted, reloads using current options.
|
|
545
|
+
* @returns {Promise<void>}
|
|
546
|
+
*/
|
|
547
|
+
reloadBackground(src) {
|
|
548
|
+
if (this.#destroyed) return Promise.resolve();
|
|
549
|
+
|
|
550
|
+
if (src !== undefined) {
|
|
551
|
+
if (this.#options.type === 'slider' && typeof src === 'string') {
|
|
552
|
+
// If the user passes a comma-separated string, split it.
|
|
553
|
+
this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];
|
|
554
|
+
} else {
|
|
555
|
+
this.#options.src = src;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
449
558
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
559
|
+
// Stop any running slider
|
|
560
|
+
if (this.#sliderIntervalId !== null) {
|
|
561
|
+
clearInterval(this.#sliderIntervalId);
|
|
562
|
+
this.#sliderIntervalId = null;
|
|
563
|
+
}
|
|
454
564
|
|
|
455
|
-
|
|
456
|
-
|
|
565
|
+
// For solid mode, we fade the loader up. For crossfade, we don't.
|
|
566
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
567
|
+
if (loader && this.#options.transitionMode === 'solid') {
|
|
568
|
+
loader.style.opacity = '1';
|
|
569
|
+
}
|
|
457
570
|
|
|
458
|
-
|
|
571
|
+
return new Promise((resolve) => {
|
|
572
|
+
const waitTime = this.#options.transitionMode === 'solid' ? this.#options.transitionDuration : 0;
|
|
573
|
+
setTimeout(() => {
|
|
574
|
+
if (this.#destroyed) return resolve();
|
|
575
|
+
|
|
576
|
+
if (this.#options.type === 'single') {
|
|
577
|
+
const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;
|
|
578
|
+
this.#loadBackground(singleSrc, false, this.#options.transitionMode === 'crossfade').then(resolve);
|
|
579
|
+
} else if (this.#options.type === 'slider') {
|
|
580
|
+
this.#startSlider();
|
|
581
|
+
resolve();
|
|
582
|
+
}
|
|
583
|
+
}, waitTime);
|
|
584
|
+
});
|
|
585
|
+
}
|
|
459
586
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
587
|
+
/**
|
|
588
|
+
* Update options on the fly without destroying the instance.
|
|
589
|
+
* @param {Object} newOptions - The new options to merge.
|
|
590
|
+
*/
|
|
591
|
+
reloadOptions(newOptions) {
|
|
592
|
+
if (this.#destroyed) return;
|
|
593
|
+
|
|
594
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
595
|
+
this.#options = { ...this.#options, ...normalized };
|
|
596
|
+
|
|
597
|
+
this.#updateCSSVariables();
|
|
598
|
+
// Restart slider if needed
|
|
599
|
+
if (this.#options.type === 'slider' && !this.#isPaused && !this.#sliderIntervalId) {
|
|
600
|
+
this.#startSlider();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
// Slider
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Start the background slideshow.
|
|
610
|
+
*/
|
|
611
|
+
#startSlider() {
|
|
612
|
+
if (this.#destroyed) return;
|
|
613
|
+
|
|
614
|
+
const sources = this.#options.src;
|
|
615
|
+
if (!Array.isArray(sources) || sources.length === 0) {
|
|
616
|
+
console.warn('[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.');
|
|
463
617
|
return;
|
|
464
618
|
}
|
|
465
619
|
|
|
466
|
-
|
|
467
|
-
|
|
620
|
+
// Load the first slide immediately
|
|
621
|
+
this.#currentSlideIndex = 0;
|
|
622
|
+
this.#loadBackground(sources[this.#currentSlideIndex]).then(() => {
|
|
623
|
+
if (this.#destroyed) return;
|
|
468
624
|
|
|
469
|
-
|
|
470
|
-
if (
|
|
471
|
-
|
|
472
|
-
|
|
625
|
+
this.#currentSlideIndex = 1;
|
|
626
|
+
if (sources.length === 1) return; // only one slide, nothing to rotate
|
|
627
|
+
|
|
628
|
+
this.#startSliderLoop();
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#startSliderLoop() {
|
|
633
|
+
const sources = this.#options.src;
|
|
634
|
+
|
|
635
|
+
// Parse as integers to prevent string concatenation bugs if users passed strings
|
|
636
|
+
const sDuration = parseInt(this.#options.sliderDuration, 10) || 8000;
|
|
637
|
+
const tDuration = parseInt(this.#options.transitionDuration, 10) || 2000;
|
|
638
|
+
const interval = sDuration + tDuration * (this.#options.transitionMode === 'solid' ? 2 : 1);
|
|
639
|
+
|
|
640
|
+
this.#sliderIntervalId = setInterval(() => {
|
|
641
|
+
if (this.#destroyed || this.#isPaused) {
|
|
473
642
|
clearInterval(this.#sliderIntervalId);
|
|
474
643
|
this.#sliderIntervalId = null;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
this.#cycleSliderSlide(sources[this.#currentSlideIndex]);
|
|
648
|
+
this.#currentSlideIndex++;
|
|
649
|
+
|
|
650
|
+
if (this.#currentSlideIndex >= sources.length) {
|
|
651
|
+
if (this.#options.sliderLoop) {
|
|
652
|
+
this.#currentSlideIndex = 0;
|
|
653
|
+
} else {
|
|
654
|
+
clearInterval(this.#sliderIntervalId);
|
|
655
|
+
this.#sliderIntervalId = null;
|
|
656
|
+
}
|
|
475
657
|
}
|
|
658
|
+
}, interval);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Cycle to a specific slide without resetting the slider instance.
|
|
663
|
+
* @param {string|ChameleonImageConfig} src - The image URL/config to load.
|
|
664
|
+
*/
|
|
665
|
+
#cycleSliderSlide(src) {
|
|
666
|
+
if (this.#destroyed) return;
|
|
667
|
+
|
|
668
|
+
if (this.#options.transitionMode === 'solid') {
|
|
669
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
670
|
+
if (loader) {
|
|
671
|
+
loader.style.opacity = '1';
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
setTimeout(() => {
|
|
675
|
+
if (this.#destroyed) return;
|
|
676
|
+
this.#loadBackground(src, false, false);
|
|
677
|
+
}, this.#options.transitionDuration);
|
|
678
|
+
} else {
|
|
679
|
+
// Crossfade mode triggers immediately
|
|
680
|
+
this.#loadBackground(src, false, true);
|
|
476
681
|
}
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Cycle to a specific slide without resetting the slider instance.
|
|
483
|
-
* @param {string} src - The image URL to load.
|
|
484
|
-
*/
|
|
485
|
-
#cycleSliderSlide(src) {
|
|
486
|
-
if (this.#destroyed) return;
|
|
487
|
-
|
|
488
|
-
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
489
|
-
if (loader) {
|
|
490
|
-
loader.style.opacity = '1';
|
|
491
|
-
}
|
|
682
|
+
}
|
|
492
683
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
// Static Helpers
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Convert legacy snake_case option keys to camelCase.
|
|
690
|
+
* @param {Object} opts
|
|
691
|
+
* @returns {Object}
|
|
692
|
+
*/
|
|
693
|
+
static #normalizeOptions(opts) {
|
|
694
|
+
const result = {};
|
|
695
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
696
|
+
const alias = ChameleonBackgrounds.#ALIASES[key];
|
|
697
|
+
result[alias ?? key] = value;
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Resolve a CSS selector string or HTMLElement to an HTMLElement.
|
|
704
|
+
* @param {string|HTMLElement} el
|
|
705
|
+
* @returns {HTMLElement}
|
|
706
|
+
*/
|
|
707
|
+
static #resolveElement(el) {
|
|
708
|
+
if (el instanceof HTMLElement) return el;
|
|
709
|
+
if (typeof el === 'string') {
|
|
710
|
+
if (el === 'body') return document.body;
|
|
711
|
+
const found = document.querySelector(el);
|
|
712
|
+
if (!found) throw new Error(`[ChameleonBackgrounds] Element not found: ${el}`);
|
|
713
|
+
return /** @type {HTMLElement} */ (found);
|
|
714
|
+
}
|
|
715
|
+
throw new TypeError(`[ChameleonBackgrounds] Invalid element: ${el}`);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Generate a short unique ID for scoping styles.
|
|
720
|
+
* @returns {string}
|
|
721
|
+
*/
|
|
722
|
+
static #generateUID() {
|
|
723
|
+
// Prefer crypto.randomUUID where available, otherwise fall back
|
|
724
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
725
|
+
return crypto.randomUUID().slice(0, 8);
|
|
726
|
+
}
|
|
727
|
+
// Fallback: random alphanumeric string
|
|
728
|
+
return Array.from({ length: 8 }, () =>
|
|
729
|
+
'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))
|
|
730
|
+
).join('');
|
|
731
|
+
}
|
|
541
732
|
}
|
|
542
|
-
// Fallback: random alphanumeric string
|
|
543
|
-
return Array.from({ length: 8 }, () =>
|
|
544
|
-
'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))
|
|
545
|
-
).join('');
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
733
|
|
|
549
734
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ChameleonBackgrounds);
|
|
550
735
|
|
|
551
736
|
|
|
552
|
-
__webpack_exports__ = __webpack_exports__["default"];
|
|
737
|
+
__webpack_exports__ = __webpack_exports__["default"];
|
|
553
738
|
/******/ return __webpack_exports__;
|
|
554
|
-
/******/
|
|
555
|
-
|
|
739
|
+
/******/
|
|
740
|
+
})()
|
|
741
|
+
;
|
|
556
742
|
});
|
|
557
743
|
//# sourceMappingURL=chameleon-backgrounds.js.map
|