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
|
@@ -1,551 +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
|
-
|
|
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
|
+
}
|
|
184
206
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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;
|
|
190
218
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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');
|
|
196
258
|
}
|
|
197
|
-
this.#element.removeChild(wrapper);
|
|
198
|
-
}
|
|
199
259
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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();
|
|
205
276
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
if (document.readyState === 'complete') {
|
|
234
|
-
this.#retrieveBackground();
|
|
235
|
-
} else {
|
|
236
|
-
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
|
+
}
|
|
237
304
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
}
|
|
262
332
|
}
|
|
263
333
|
|
|
264
|
-
|
|
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 {
|
|
265
353
|
z-index: 2;
|
|
266
354
|
position: relative;
|
|
267
355
|
}
|
|
268
|
-
|
|
269
|
-
.cbg-loader-${uid} {
|
|
356
|
+
.cbg-loader {
|
|
270
357
|
height: 100%;
|
|
271
358
|
width: 100%;
|
|
272
|
-
position:
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
275
364
|
opacity: 1;
|
|
276
365
|
z-index: 1;
|
|
277
|
-
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;
|
|
278
374
|
top: 0;
|
|
279
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;
|
|
280
388
|
}
|
|
281
389
|
`;
|
|
282
390
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Wrap existing content and insert the overlay loader.
|
|
292
|
-
*/
|
|
293
|
-
#buildDOM() {
|
|
294
|
-
const uid = this.#uid;
|
|
295
|
-
|
|
296
|
-
const wrapper = document.createElement('div');
|
|
297
|
-
wrapper.id = `cbg-inner-${uid}`;
|
|
298
|
-
|
|
299
|
-
// Move all child nodes from the element into the wrapper
|
|
300
|
-
while (this.#element.firstChild) {
|
|
301
|
-
wrapper.appendChild(this.#element.firstChild);
|
|
302
|
-
}
|
|
391
|
+
const style = document.createElement('style');
|
|
392
|
+
style.id = 'chameleon-global-styles';
|
|
393
|
+
style.textContent = css;
|
|
394
|
+
document.head.appendChild(style);
|
|
395
|
+
}
|
|
303
396
|
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
410
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.#element.appendChild(loader);
|
|
310
|
-
}
|
|
411
|
+
this.#element.style.setProperty('--cbg-min-overlay', String(this.#options.minOverlay));
|
|
412
|
+
}
|
|
311
413
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Wrap existing content and insert the overlay loader.
|
|
416
|
+
*/
|
|
417
|
+
#buildDOM() {
|
|
418
|
+
const uid = this.#uid;
|
|
315
419
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
#retrieveBackground() {
|
|
320
|
-
if (this.#destroyed) return;
|
|
420
|
+
const wrapper = document.createElement('div');
|
|
421
|
+
wrapper.id = `cbg-inner-${uid}`;
|
|
422
|
+
wrapper.classList.add('cbg-inner');
|
|
321
423
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
}
|
|
424
|
+
// Move all child nodes from the element into the wrapper
|
|
425
|
+
while (this.#element.firstChild) {
|
|
426
|
+
wrapper.appendChild(this.#element.firstChild);
|
|
427
|
+
}
|
|
328
428
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
* @param {string} src - Image URL to load.
|
|
332
|
-
* @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).
|
|
333
|
-
* @returns {Promise<void>}
|
|
334
|
-
*/
|
|
335
|
-
#loadBackground(src, isFirst = true) {
|
|
336
|
-
if (this.#destroyed) return Promise.resolve();
|
|
429
|
+
const loader = document.createElement('div');
|
|
430
|
+
loader.classList.add('cbg-loader', `cbg-loader-${uid}`);
|
|
337
431
|
|
|
338
|
-
|
|
339
|
-
|
|
432
|
+
// Append wrapper and loader
|
|
433
|
+
this.#element.appendChild(wrapper);
|
|
434
|
+
this.#element.appendChild(loader);
|
|
435
|
+
}
|
|
340
436
|
|
|
341
|
-
|
|
342
|
-
|
|
437
|
+
// ---------------------------------------------------------------------------
|
|
438
|
+
// Background Loading
|
|
439
|
+
// ---------------------------------------------------------------------------
|
|
343
440
|
|
|
344
|
-
|
|
345
|
-
|
|
441
|
+
/**
|
|
442
|
+
* Route to the correct loader based on type.
|
|
443
|
+
*/
|
|
444
|
+
#retrieveBackground() {
|
|
445
|
+
if (this.#destroyed) return;
|
|
346
446
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
447
|
+
if (this.#options.type === 'single') {
|
|
448
|
+
const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;
|
|
449
|
+
this.#loadBackground(singleSrc);
|
|
450
|
+
} else if (this.#options.type === 'slider') {
|
|
451
|
+
this.#startSlider();
|
|
350
452
|
}
|
|
453
|
+
}
|
|
351
454
|
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
475
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
};
|
|
476
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
477
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
478
|
+
}
|
|
359
479
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
});
|
|
379
539
|
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Stop any running slider
|
|
383
|
-
if (this.#sliderIntervalId !== null) {
|
|
384
|
-
clearInterval(this.#sliderIntervalId);
|
|
385
|
-
this.#sliderIntervalId = null;
|
|
386
|
-
}
|
|
387
540
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (this.#destroyed) return resolve();
|
|
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
|
+
}
|
|
396
558
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.#
|
|
400
|
-
|
|
401
|
-
this.#startSlider();
|
|
402
|
-
resolve();
|
|
559
|
+
// Stop any running slider
|
|
560
|
+
if (this.#sliderIntervalId !== null) {
|
|
561
|
+
clearInterval(this.#sliderIntervalId);
|
|
562
|
+
this.#sliderIntervalId = null;
|
|
403
563
|
}
|
|
404
|
-
}, this.#options.transitionDuration);
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Update options on the fly without destroying the instance.
|
|
410
|
-
* @param {Object} newOptions - The new options to merge.
|
|
411
|
-
*/
|
|
412
|
-
reloadOptions(newOptions) {
|
|
413
|
-
if (this.#destroyed) return;
|
|
414
|
-
|
|
415
|
-
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
416
|
-
this.#options = { ...this.#options, ...normalized };
|
|
417
|
-
|
|
418
|
-
// Remove old styles
|
|
419
|
-
if (this.#styleElement?.parentNode) {
|
|
420
|
-
this.#styleElement.parentNode.removeChild(this.#styleElement);
|
|
421
|
-
this.#styleElement = null;
|
|
422
|
-
}
|
|
423
564
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
}
|
|
427
570
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
}
|
|
431
586
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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;
|
|
437
593
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
console.warn('[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.');
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
594
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
595
|
+
this.#options = { ...this.#options, ...normalized };
|
|
443
596
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
597
|
+
this.#updateCSSVariables();
|
|
598
|
+
// Restart slider if needed
|
|
599
|
+
if (this.#options.type === 'slider' && !this.#isPaused && !this.#sliderIntervalId) {
|
|
600
|
+
this.#startSlider();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
448
603
|
|
|
449
|
-
|
|
450
|
-
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
// Slider
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
451
607
|
|
|
452
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Start the background slideshow.
|
|
610
|
+
*/
|
|
611
|
+
#startSlider() {
|
|
612
|
+
if (this.#destroyed) return;
|
|
453
613
|
|
|
454
|
-
|
|
455
|
-
if (
|
|
456
|
-
|
|
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`.');
|
|
457
617
|
return;
|
|
458
618
|
}
|
|
459
619
|
|
|
460
|
-
|
|
461
|
-
|
|
620
|
+
// Load the first slide immediately
|
|
621
|
+
this.#currentSlideIndex = 0;
|
|
622
|
+
this.#loadBackground(sources[this.#currentSlideIndex]).then(() => {
|
|
623
|
+
if (this.#destroyed) return;
|
|
462
624
|
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
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) {
|
|
467
642
|
clearInterval(this.#sliderIntervalId);
|
|
468
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
|
+
}
|
|
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';
|
|
469
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);
|
|
470
681
|
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Cycle to a specific slide without resetting the slider instance.
|
|
477
|
-
* @param {string} src - The image URL to load.
|
|
478
|
-
*/
|
|
479
|
-
#cycleSliderSlide(src) {
|
|
480
|
-
if (this.#destroyed) return;
|
|
481
|
-
|
|
482
|
-
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
483
|
-
if (loader) {
|
|
484
|
-
loader.style.opacity = '1';
|
|
485
|
-
}
|
|
682
|
+
}
|
|
486
683
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
535
732
|
}
|
|
536
|
-
// Fallback: random alphanumeric string
|
|
537
|
-
return Array.from({ length: 8 }, () =>
|
|
538
|
-
'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))
|
|
539
|
-
).join('');
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
733
|
|
|
543
734
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ChameleonBackgrounds);
|
|
544
735
|
|
|
545
736
|
|
|
546
|
-
__webpack_exports__ = __webpack_exports__["default"];
|
|
737
|
+
__webpack_exports__ = __webpack_exports__["default"];
|
|
547
738
|
/******/ return __webpack_exports__;
|
|
548
|
-
/******/
|
|
549
|
-
|
|
739
|
+
/******/
|
|
740
|
+
})()
|
|
741
|
+
;
|
|
550
742
|
});
|
|
551
743
|
//# sourceMappingURL=chameleon-backgrounds.js.map
|