chameleon-backgrounds 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,3 +1,72 @@
|
|
|
1
|
+
(function webpackUniversalModuleDefinition(root, factory) {
|
|
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
|
+
})(this, () => {
|
|
11
|
+
return /******/ (() => { // webpackBootstrap
|
|
12
|
+
/******/ "use strict";
|
|
13
|
+
/******/ // The require scope
|
|
14
|
+
/******/ const __webpack_require__ = {};
|
|
15
|
+
/******/
|
|
16
|
+
/************************************************************************/
|
|
17
|
+
/******/ /* webpack/runtime/define property getters */
|
|
18
|
+
/******/ (() => {
|
|
19
|
+
/******/ // define getter/value functions for harmony exports
|
|
20
|
+
/******/ __webpack_require__.d = (exports, definition) => {
|
|
21
|
+
/******/ if(Array.isArray(definition)) {
|
|
22
|
+
/******/ var i = 0;
|
|
23
|
+
/******/ while(i < definition.length) {
|
|
24
|
+
/******/ var key = definition[i++];
|
|
25
|
+
/******/ var binding = definition[i++];
|
|
26
|
+
/******/ if(!__webpack_require__.o(exports, key)) {
|
|
27
|
+
/******/ if(binding === 0) {
|
|
28
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] });
|
|
29
|
+
/******/ } else {
|
|
30
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding });
|
|
31
|
+
/******/ }
|
|
32
|
+
/******/ } else if(binding === 0) { i++; }
|
|
33
|
+
/******/ }
|
|
34
|
+
/******/ } else {
|
|
35
|
+
/******/ for(var key in definition) {
|
|
36
|
+
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
37
|
+
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
38
|
+
/******/ }
|
|
39
|
+
/******/ }
|
|
40
|
+
/******/ }
|
|
41
|
+
/******/ };
|
|
42
|
+
/******/ })();
|
|
43
|
+
/******/
|
|
44
|
+
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
45
|
+
/******/ (() => {
|
|
46
|
+
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
47
|
+
/******/ })();
|
|
48
|
+
/******/
|
|
49
|
+
/******/ /* webpack/runtime/make namespace object */
|
|
50
|
+
/******/ (() => {
|
|
51
|
+
/******/ // define __esModule on exports
|
|
52
|
+
/******/ __webpack_require__.r = (exports) => {
|
|
53
|
+
/******/ if(Symbol.toStringTag) {
|
|
54
|
+
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
55
|
+
/******/ }
|
|
56
|
+
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
57
|
+
/******/ };
|
|
58
|
+
/******/ })();
|
|
59
|
+
/******/
|
|
60
|
+
/************************************************************************/
|
|
61
|
+
let __webpack_exports__ = {};
|
|
62
|
+
/*!**************************************!*\
|
|
63
|
+
!*** ./src/chameleon-backgrounds.js ***!
|
|
64
|
+
\**************************************/
|
|
65
|
+
__webpack_require__.r(__webpack_exports__);
|
|
66
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
67
|
+
/* harmony export */ ChameleonBackgrounds: () => (/* binding */ ChameleonBackgrounds),
|
|
68
|
+
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
69
|
+
/* harmony export */ });
|
|
1
70
|
/**
|
|
2
71
|
* _____ _ _
|
|
3
72
|
* / ____| | | |
|
|
@@ -13,42 +82,32 @@
|
|
|
13
82
|
* __/ |
|
|
14
83
|
* |___/
|
|
15
84
|
*
|
|
16
|
-
* ChameleonBackgrounds
|
|
17
|
-
*
|
|
18
|
-
* images with elegant fade-in transitions and slideshow support.
|
|
19
|
-
*
|
|
85
|
+
* @module ChameleonBackgrounds
|
|
86
|
+
* @version 2.1.2
|
|
20
87
|
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
21
88
|
* @license MIT
|
|
22
89
|
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
90
|
+
*
|
|
91
|
+
* A zero-dependency JavaScript library to dynamically load background
|
|
92
|
+
* images with elegant fade-in transitions and slideshow support.
|
|
23
93
|
*/
|
|
24
|
-
(function (root, factory) {
|
|
25
|
-
if (typeof define === 'function' && define.amd) {
|
|
26
|
-
// AMD
|
|
27
|
-
define([], factory);
|
|
28
|
-
} else if (typeof module === 'object' && module.exports) {
|
|
29
|
-
// CommonJS
|
|
30
|
-
module.exports = factory();
|
|
31
|
-
} else {
|
|
32
|
-
// Browser global
|
|
33
|
-
root.ChameleonBackgrounds = factory();
|
|
34
|
-
}
|
|
35
|
-
}(typeof self !== 'undefined' ? self : this, function () {
|
|
36
|
-
'use strict';
|
|
37
94
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
95
|
+
/**
|
|
96
|
+
* @typedef {Object} ChameleonOptions
|
|
97
|
+
* @property {string|HTMLElement} element - CSS selector or DOM element to attach to.
|
|
98
|
+
* @property {'single'|'slider'} type - Background mode.
|
|
99
|
+
* @property {string|string[]} src - Image URL(s). String for 'single', array for 'slider'.
|
|
100
|
+
* @property {string} overlayColor - Overlay color (hex, rgb, rgba, hsl).
|
|
101
|
+
* @property {string} [overlayImage] - Optional overlay pattern image URL.
|
|
102
|
+
* @property {number} [minOverlay=0] - Minimum overlay opacity after fade (0–1).
|
|
103
|
+
* @property {number} [transitionDuration=2000] - Fade duration in milliseconds.
|
|
104
|
+
* @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.
|
|
105
|
+
* @property {boolean} [sliderLoop=false] - Whether the slider restarts after the last slide.
|
|
106
|
+
*/
|
|
50
107
|
|
|
51
|
-
|
|
108
|
+
class ChameleonBackgrounds {
|
|
109
|
+
/** @type {ChameleonOptions} */
|
|
110
|
+
static #DEFAULTS = Object.freeze({
|
|
52
111
|
element: 'body',
|
|
53
112
|
type: 'single',
|
|
54
113
|
src: '',
|
|
@@ -60,7 +119,8 @@
|
|
|
60
119
|
sliderLoop: false,
|
|
61
120
|
});
|
|
62
121
|
|
|
63
|
-
|
|
122
|
+
// Legacy snake_case → camelCase alias map
|
|
123
|
+
static #ALIASES = Object.freeze({
|
|
64
124
|
transition_duration: 'transitionDuration',
|
|
65
125
|
slider_duration: 'sliderDuration',
|
|
66
126
|
slider_loop: 'sliderLoop',
|
|
@@ -69,288 +129,423 @@
|
|
|
69
129
|
overlay_image: 'overlayImage',
|
|
70
130
|
});
|
|
71
131
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
*/
|
|
75
|
-
function normalizeOptions(opts) {
|
|
76
|
-
const result = {};
|
|
77
|
-
for (const key of Object.keys(opts)) {
|
|
78
|
-
const alias = ALIASES[key];
|
|
79
|
-
result[alias !== undefined ? alias : key] = opts[key];
|
|
80
|
-
}
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
132
|
+
/** @type {ChameleonOptions} */
|
|
133
|
+
#options;
|
|
83
134
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
function resolveElement(el) {
|
|
88
|
-
if (el instanceof HTMLElement) return el;
|
|
89
|
-
if (typeof el === 'string') {
|
|
90
|
-
if (el === 'body') return document.body;
|
|
91
|
-
var found = document.querySelector(el);
|
|
92
|
-
if (!found) throw new Error('[ChameleonBackgrounds] Element not found: ' + el);
|
|
93
|
-
return found;
|
|
94
|
-
}
|
|
95
|
-
throw new TypeError('[ChameleonBackgrounds] Invalid element: ' + el);
|
|
96
|
-
}
|
|
135
|
+
/** @type {HTMLElement} */
|
|
136
|
+
#element;
|
|
97
137
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
*/
|
|
101
|
-
function generateUID() {
|
|
102
|
-
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
103
|
-
return crypto.randomUUID().slice(0, 8);
|
|
104
|
-
}
|
|
105
|
-
return Array.from({ length: 8 }, function () {
|
|
106
|
-
return 'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36));
|
|
107
|
-
}).join('');
|
|
108
|
-
}
|
|
138
|
+
/** @type {string} */
|
|
139
|
+
#uid;
|
|
109
140
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// -------------------------------------------------------------------------
|
|
141
|
+
/** @type {string} */
|
|
142
|
+
#originalHTML;
|
|
113
143
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return new ChameleonBackgrounds(options);
|
|
117
|
-
}
|
|
144
|
+
/** @type {HTMLStyleElement|null} */
|
|
145
|
+
#styleElement = null;
|
|
118
146
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
for (var k in DEFAULTS) {
|
|
122
|
-
if (DEFAULTS.hasOwnProperty(k)) {
|
|
123
|
-
this._options[k] = normalized[k] !== undefined ? normalized[k] : DEFAULTS[k];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
147
|
+
/** @type {number|null} */
|
|
148
|
+
#sliderIntervalId = null;
|
|
126
149
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this._originalHTML = this._element.innerHTML;
|
|
130
|
-
this._styleElement = null;
|
|
131
|
-
this._sliderIntervalId = null;
|
|
132
|
-
this._destroyed = false;
|
|
150
|
+
/** @type {boolean} */
|
|
151
|
+
#destroyed = false;
|
|
133
152
|
|
|
134
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Create a new ChameleonBackgrounds instance.
|
|
155
|
+
* @param {Partial<ChameleonOptions>} [options]
|
|
156
|
+
*/
|
|
157
|
+
constructor(options = {}) {
|
|
158
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(options);
|
|
159
|
+
this.#options = { ...ChameleonBackgrounds.#DEFAULTS, ...normalized };
|
|
160
|
+
this.#uid = ChameleonBackgrounds.#generateUID();
|
|
161
|
+
this.#element = ChameleonBackgrounds.#resolveElement(this.#options.element);
|
|
162
|
+
this.#originalHTML = this.#element.innerHTML;
|
|
163
|
+
|
|
164
|
+
this.#init();
|
|
135
165
|
}
|
|
136
166
|
|
|
137
|
-
//
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
138
168
|
// Public API
|
|
139
|
-
//
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
140
170
|
|
|
141
171
|
/**
|
|
142
172
|
* Clean up all injected DOM, styles, and intervals.
|
|
143
173
|
* Restores the target element to its original state.
|
|
144
174
|
*/
|
|
145
|
-
|
|
146
|
-
if (this
|
|
147
|
-
this
|
|
175
|
+
destroy() {
|
|
176
|
+
if (this.#destroyed) return;
|
|
177
|
+
this.#destroyed = true;
|
|
178
|
+
|
|
179
|
+
// Stop any running slider
|
|
180
|
+
if (this.#sliderIntervalId !== null) {
|
|
181
|
+
clearInterval(this.#sliderIntervalId);
|
|
182
|
+
this.#sliderIntervalId = null;
|
|
183
|
+
}
|
|
148
184
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.
|
|
185
|
+
// Remove injected style element
|
|
186
|
+
if (this.#styleElement?.parentNode) {
|
|
187
|
+
this.#styleElement.parentNode.removeChild(this.#styleElement);
|
|
188
|
+
this.#styleElement = null;
|
|
152
189
|
}
|
|
153
190
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
191
|
+
// Unwrap the original content instead of resetting innerHTML
|
|
192
|
+
const wrapper = this.#element.querySelector(`#cbg-inner-${this.#uid}`);
|
|
193
|
+
if (wrapper && wrapper.parentNode === this.#element) {
|
|
194
|
+
while (wrapper.firstChild) {
|
|
195
|
+
this.#element.insertBefore(wrapper.firstChild, wrapper);
|
|
196
|
+
}
|
|
197
|
+
this.#element.removeChild(wrapper);
|
|
157
198
|
}
|
|
158
199
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
200
|
+
// Remove loader
|
|
201
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
202
|
+
if (loader) {
|
|
203
|
+
loader.remove();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Remove inline background-image
|
|
207
|
+
this.#element.style.backgroundImage = '';
|
|
208
|
+
}
|
|
162
209
|
|
|
163
210
|
/**
|
|
164
|
-
*
|
|
211
|
+
* The resolved options (read-only copy).
|
|
212
|
+
* @returns {ChameleonOptions}
|
|
165
213
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (this._options.hasOwnProperty(k)) {
|
|
170
|
-
copy[k] = this._options[k];
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return copy;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// -------------------------------------------------------------------------
|
|
177
|
-
// Initialization (private by convention)
|
|
178
|
-
// -------------------------------------------------------------------------
|
|
214
|
+
get options() {
|
|
215
|
+
return { ...this.#options };
|
|
216
|
+
}
|
|
179
217
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Initialization
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
183
221
|
|
|
184
|
-
|
|
222
|
+
#init() {
|
|
223
|
+
this.#injectStyles();
|
|
224
|
+
this.#buildDOM();
|
|
185
225
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
226
|
+
// If the element is NOT <body>, wait for window load to start loading images.
|
|
227
|
+
// For <body>, the loader is already visible, so we start immediately to avoid
|
|
228
|
+
// the re-execution issue the legacy lib had with body scripts.
|
|
229
|
+
if (this.#element.matches('body')) {
|
|
230
|
+
// Use a microtask so the DOM changes above settle first.
|
|
231
|
+
queueMicrotask(() => this.#retrieveBackground());
|
|
189
232
|
} else {
|
|
190
233
|
if (document.readyState === 'complete') {
|
|
191
|
-
this
|
|
234
|
+
this.#retrieveBackground();
|
|
192
235
|
} else {
|
|
193
|
-
window.addEventListener('load',
|
|
194
|
-
window.removeEventListener('load', onLoad);
|
|
195
|
-
self._retrieveBackground();
|
|
196
|
-
});
|
|
236
|
+
window.addEventListener('load', () => this.#retrieveBackground(), { once: true });
|
|
197
237
|
}
|
|
198
238
|
}
|
|
199
|
-
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// DOM Construction
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
200
244
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Inject a scoped <style> element into the <head>.
|
|
247
|
+
*/
|
|
248
|
+
#injectStyles() {
|
|
249
|
+
const uid = this.#uid;
|
|
250
|
+
const selector = this.#options.element === 'body' || this.#element.matches('body')
|
|
204
251
|
? 'body'
|
|
205
|
-
: this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
?
|
|
252
|
+
: this.#options.element;
|
|
253
|
+
const duration = this.#options.transitionDuration / 1000; // ms → s
|
|
254
|
+
const position = this.#element.matches('body') ? 'fixed' : 'absolute';
|
|
255
|
+
const overlayBg = this.#options.overlayImage
|
|
256
|
+
? `url(${this.#options.overlayImage})`
|
|
210
257
|
: 'none';
|
|
211
258
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
259
|
+
const css = `
|
|
260
|
+
${typeof selector === 'string' ? selector : `.cbg-host-${uid}`} {
|
|
261
|
+
position: relative;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#cbg-inner-${uid} {
|
|
265
|
+
z-index: 2;
|
|
266
|
+
position: relative;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.cbg-loader-${uid} {
|
|
270
|
+
height: 100%;
|
|
271
|
+
width: 100%;
|
|
272
|
+
position: ${position};
|
|
273
|
+
background-image: ${overlayBg};
|
|
274
|
+
background-color: ${this.#options.overlayColor};
|
|
275
|
+
opacity: 1;
|
|
276
|
+
z-index: 1;
|
|
277
|
+
transition: opacity ${duration}s ease;
|
|
278
|
+
top: 0;
|
|
279
|
+
left: 0;
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
|
|
283
|
+
const style = document.createElement('style');
|
|
284
|
+
style.dataset.chameleonUid = uid;
|
|
229
285
|
style.textContent = css;
|
|
230
286
|
document.head.appendChild(style);
|
|
231
|
-
this
|
|
232
|
-
}
|
|
287
|
+
this.#styleElement = style;
|
|
288
|
+
}
|
|
233
289
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Wrap existing content and insert the overlay loader.
|
|
292
|
+
*/
|
|
293
|
+
#buildDOM() {
|
|
294
|
+
const uid = this.#uid;
|
|
237
295
|
|
|
238
|
-
|
|
239
|
-
wrapper.id =
|
|
240
|
-
wrapper.innerHTML = content;
|
|
296
|
+
const wrapper = document.createElement('div');
|
|
297
|
+
wrapper.id = `cbg-inner-${uid}`;
|
|
241
298
|
|
|
242
|
-
|
|
243
|
-
|
|
299
|
+
// Move all child nodes from the element into the wrapper
|
|
300
|
+
while (this.#element.firstChild) {
|
|
301
|
+
wrapper.appendChild(this.#element.firstChild);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const loader = document.createElement('div');
|
|
305
|
+
loader.classList.add(`cbg-loader-${uid}`);
|
|
244
306
|
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
this.
|
|
248
|
-
}
|
|
307
|
+
// Append wrapper and loader
|
|
308
|
+
this.#element.appendChild(wrapper);
|
|
309
|
+
this.#element.appendChild(loader);
|
|
310
|
+
}
|
|
249
311
|
|
|
250
|
-
//
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
251
313
|
// Background Loading
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.
|
|
262
|
-
} else if (this.
|
|
263
|
-
this
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Route to the correct loader based on type.
|
|
318
|
+
*/
|
|
319
|
+
#retrieveBackground() {
|
|
320
|
+
if (this.#destroyed) return;
|
|
321
|
+
|
|
322
|
+
if (this.#options.type === 'single') {
|
|
323
|
+
this.#loadBackground(typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0]);
|
|
324
|
+
} else if (this.#options.type === 'slider') {
|
|
325
|
+
this.#startSlider();
|
|
264
326
|
}
|
|
265
|
-
}
|
|
327
|
+
}
|
|
266
328
|
|
|
267
|
-
|
|
268
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Preload an image and apply it as the element's background.
|
|
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();
|
|
269
337
|
|
|
270
|
-
|
|
271
|
-
|
|
338
|
+
return new Promise((resolve) => {
|
|
339
|
+
const img = new Image();
|
|
272
340
|
|
|
273
|
-
|
|
274
|
-
|
|
341
|
+
img.onload = () => {
|
|
342
|
+
if (this.#destroyed) return resolve();
|
|
275
343
|
|
|
276
|
-
|
|
277
|
-
|
|
344
|
+
this.#element.style.backgroundImage = `url(${src})`;
|
|
345
|
+
this.#element.style.backgroundSize = 'cover';
|
|
278
346
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
347
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
348
|
+
if (loader) {
|
|
349
|
+
loader.style.opacity = String(this.#options.minOverlay);
|
|
350
|
+
}
|
|
283
351
|
|
|
284
|
-
|
|
285
|
-
|
|
352
|
+
resolve();
|
|
353
|
+
};
|
|
286
354
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
355
|
+
img.onerror = () => {
|
|
356
|
+
console.warn(`[ChameleonBackgrounds] Failed to load image: ${src}`);
|
|
357
|
+
resolve();
|
|
358
|
+
};
|
|
291
359
|
|
|
292
|
-
|
|
293
|
-
|
|
360
|
+
img.src = src;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
294
363
|
|
|
295
|
-
|
|
296
|
-
|
|
364
|
+
/**
|
|
365
|
+
/**
|
|
366
|
+
* Reload background with a fade-out → swap → fade-in cycle.
|
|
367
|
+
* @param {string} [src] - Optional new image URL. If omitted, reloads using current options.
|
|
368
|
+
* @returns {Promise<void>}
|
|
369
|
+
*/
|
|
370
|
+
reloadBackground(src) {
|
|
371
|
+
if (this.#destroyed) return Promise.resolve();
|
|
297
372
|
|
|
298
|
-
|
|
373
|
+
if (src !== undefined) {
|
|
374
|
+
if (this.#options.type === 'slider' && typeof src === 'string') {
|
|
375
|
+
// If the user passes a comma-separated string, split it. Otherwise wrap it.
|
|
376
|
+
this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];
|
|
377
|
+
} else {
|
|
378
|
+
this.#options.src = src;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Stop any running slider
|
|
383
|
+
if (this.#sliderIntervalId !== null) {
|
|
384
|
+
clearInterval(this.#sliderIntervalId);
|
|
385
|
+
this.#sliderIntervalId = null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
299
389
|
if (loader) {
|
|
300
390
|
loader.style.opacity = '1';
|
|
301
391
|
}
|
|
302
392
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
393
|
+
return new Promise((resolve) => {
|
|
394
|
+
setTimeout(() => {
|
|
395
|
+
if (this.#destroyed) return resolve();
|
|
396
|
+
|
|
397
|
+
if (this.#options.type === 'single') {
|
|
398
|
+
const singleSrc = typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0];
|
|
399
|
+
this.#loadBackground(singleSrc, false).then(resolve);
|
|
400
|
+
} else if (this.#options.type === 'slider') {
|
|
401
|
+
this.#startSlider();
|
|
402
|
+
resolve();
|
|
403
|
+
}
|
|
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
|
+
|
|
424
|
+
// Inject updated styles
|
|
425
|
+
this.#injectStyles();
|
|
426
|
+
}
|
|
309
427
|
|
|
310
|
-
//
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
311
429
|
// Slider
|
|
312
|
-
//
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
313
431
|
|
|
314
|
-
|
|
315
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Start the background slideshow.
|
|
434
|
+
*/
|
|
435
|
+
#startSlider() {
|
|
436
|
+
if (this.#destroyed) return;
|
|
316
437
|
|
|
317
|
-
|
|
438
|
+
const sources = this.#options.src;
|
|
318
439
|
if (!Array.isArray(sources) || sources.length === 0) {
|
|
319
440
|
console.warn('[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.');
|
|
320
441
|
return;
|
|
321
442
|
}
|
|
322
443
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (self._destroyed) return;
|
|
444
|
+
// Load the first slide immediately
|
|
445
|
+
let index = 0;
|
|
446
|
+
this.#loadBackground(sources[index]).then(() => {
|
|
447
|
+
if (this.#destroyed) return;
|
|
328
448
|
|
|
329
449
|
index = 1;
|
|
330
|
-
if (sources.length === 1) return;
|
|
450
|
+
if (sources.length === 1) return; // only one slide, nothing to rotate
|
|
331
451
|
|
|
332
|
-
|
|
452
|
+
const interval = this.#options.sliderDuration + this.#options.transitionDuration * 2;
|
|
333
453
|
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
clearInterval(
|
|
454
|
+
this.#sliderIntervalId = setInterval(() => {
|
|
455
|
+
if (this.#destroyed) {
|
|
456
|
+
clearInterval(this.#sliderIntervalId);
|
|
337
457
|
return;
|
|
338
458
|
}
|
|
339
459
|
|
|
340
|
-
|
|
460
|
+
this.#cycleSliderSlide(sources[index]);
|
|
341
461
|
index++;
|
|
342
462
|
|
|
343
463
|
if (index >= sources.length) {
|
|
344
|
-
if (
|
|
464
|
+
if (this.#options.sliderLoop) {
|
|
345
465
|
index = 0;
|
|
346
466
|
} else {
|
|
347
|
-
clearInterval(
|
|
348
|
-
|
|
467
|
+
clearInterval(this.#sliderIntervalId);
|
|
468
|
+
this.#sliderIntervalId = null;
|
|
349
469
|
}
|
|
350
470
|
}
|
|
351
471
|
}, interval);
|
|
352
472
|
});
|
|
353
|
-
}
|
|
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
|
+
}
|
|
486
|
+
|
|
487
|
+
setTimeout(() => {
|
|
488
|
+
if (this.#destroyed) return;
|
|
489
|
+
this.#loadBackground(src, false);
|
|
490
|
+
}, this.#options.transitionDuration);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// Static Helpers
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Convert legacy snake_case option keys to camelCase.
|
|
499
|
+
* @param {Object} opts
|
|
500
|
+
* @returns {Object}
|
|
501
|
+
*/
|
|
502
|
+
static #normalizeOptions(opts) {
|
|
503
|
+
const result = {};
|
|
504
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
505
|
+
const alias = ChameleonBackgrounds.#ALIASES[key];
|
|
506
|
+
result[alias ?? key] = value;
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Resolve a CSS selector string or HTMLElement to an HTMLElement.
|
|
513
|
+
* @param {string|HTMLElement} el
|
|
514
|
+
* @returns {HTMLElement}
|
|
515
|
+
*/
|
|
516
|
+
static #resolveElement(el) {
|
|
517
|
+
if (el instanceof HTMLElement) return el;
|
|
518
|
+
if (typeof el === 'string') {
|
|
519
|
+
if (el === 'body') return document.body;
|
|
520
|
+
const found = document.querySelector(el);
|
|
521
|
+
if (!found) throw new Error(`[ChameleonBackgrounds] Element not found: ${el}`);
|
|
522
|
+
return /** @type {HTMLElement} */ (found);
|
|
523
|
+
}
|
|
524
|
+
throw new TypeError(`[ChameleonBackgrounds] Invalid element: ${el}`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Generate a short unique ID for scoping styles.
|
|
529
|
+
* @returns {string}
|
|
530
|
+
*/
|
|
531
|
+
static #generateUID() {
|
|
532
|
+
// Prefer crypto.randomUUID where available, otherwise fall back
|
|
533
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
534
|
+
return crypto.randomUUID().slice(0, 8);
|
|
535
|
+
}
|
|
536
|
+
// Fallback: random alphanumeric string
|
|
537
|
+
return Array.from({ length: 8 }, () =>
|
|
538
|
+
'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))
|
|
539
|
+
).join('');
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ChameleonBackgrounds);
|
|
544
|
+
|
|
354
545
|
|
|
355
|
-
|
|
356
|
-
|
|
546
|
+
__webpack_exports__ = __webpack_exports__["default"];
|
|
547
|
+
/******/ return __webpack_exports__;
|
|
548
|
+
/******/ })()
|
|
549
|
+
;
|
|
550
|
+
});
|
|
551
|
+
//# sourceMappingURL=chameleon-backgrounds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chameleon-backgrounds.js","mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;;UCVA;UACA;;;;;WCDA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA,2CAA2C,0CAA0C;WACrF,MAAM;WACN,2CAA2C,gCAAgC;WAC3E;WACA,KAAK,yBAAyB;WAC9B;WACA,GAAG;WACH;WACA;WACA,0CAA0C,wCAAwC;WAClF;WACA;WACA;WACA,E;;;;;WCtBA,wF;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D,E;;;;;;;;;;;;;ACNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,aAAa,QAAQ;AACrB,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC,cAAc,yBAAyB;AACvC;;AAEA;AACA,aAAa,kBAAkB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH,aAAa,kBAAkB;AAC/B;;AAEA,aAAa,aAAa;AAC1B;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,uBAAuB;AACpC;;AAEA,aAAa,aAAa;AAC1B;;AAEA,aAAa,SAAS;AACtB;;AAEA;AACA;AACA,aAAa,2BAA2B;AACxC;AACA,0BAA0B;AAC1B;AACA,sBAAsB;AACtB;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,8DAA8D,UAAU;AACxE;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,8DAA8D,UAAU;AACxE;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,eAAe;AACf;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,QAAQ;AACR,4EAA4E,YAAY;AACxF;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D;AAC9D;AACA;AACA,eAAe,2BAA2B;AAC1C;;AAEA;AACA,QAAQ,uDAAuD,IAAI;AACnE;AACA;;AAEA,mBAAmB;AACnB;AACA;AACA;;AAEA,oBAAoB;AACpB;AACA;AACA,oBAAoB;AACpB,4BAA4B;AAC5B,4BAA4B;AAC5B;AACA;AACA,8BAA8B,SAAS;AACvC;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,8BAA8B,IAAI;;AAElC;AACA;AACA;AACA;;AAEA;AACA,uCAAuC,IAAI;;AAE3C;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA,aAAa,UAAU;AACvB,aAAa,UAAU;AACvB,eAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA,qDAAqD,IAAI;AACzD;;AAEA,kEAAkE,UAAU;AAC5E;AACA;AACA;;AAEA;AACA;;AAEA;AACA,qEAAqE,IAAI;AACzE;AACA;;AAEA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA,aAAa,QAAQ;AACrB,eAAe;AACf;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA,8DAA8D,UAAU;AACxE;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA,OAAO;AACP,KAAK;AACL;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;;AAEA;AACA,sBAAsB;;AAEtB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,wCAAwC;;AAExC;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;AACA,OAAO;AACP,KAAK;AACL;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;;AAEA,8DAA8D,UAAU;AACxE;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB,eAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,oBAAoB;AACjC,eAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA,+EAA+E,GAAG;AAClF,wBAAwB,aAAa;AACrC;AACA,mEAAmE,GAAG;AACtE;;AAEA;AACA;AACA,eAAe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,WAAW;AACnC;AACA;AACA;AACA;;AAEA,iEAAe,oBAAoB,EAAC;AACJ","sources":["webpack://ChameleonBackgrounds/webpack/universalModuleDefinition","webpack://ChameleonBackgrounds/webpack/bootstrap","webpack://ChameleonBackgrounds/webpack/runtime/define property getters","webpack://ChameleonBackgrounds/webpack/runtime/hasOwnProperty shorthand","webpack://ChameleonBackgrounds/webpack/runtime/make namespace object","webpack://ChameleonBackgrounds/./src/chameleon-backgrounds.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ChameleonBackgrounds\"] = factory();\n\telse\n\t\troot[\"ChameleonBackgrounds\"] = factory();\n})(this, () => {\nreturn ","// The require scope\nconst __webpack_require__ = {};\n\n","// define getter/value functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tif(Array.isArray(definition)) {\n\t\tvar i = 0;\n\t\twhile(i < definition.length) {\n\t\t\tvar key = definition[i++];\n\t\t\tvar binding = definition[i++];\n\t\t\tif(!__webpack_require__.o(exports, key)) {\n\t\t\t\tif(binding === 0) {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, value: definition[i++] });\n\t\t\t\t} else {\n\t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: binding });\n\t\t\t\t}\n\t\t\t} else if(binding === 0) { i++; }\n\t\t}\n\t} else {\n\t\tfor(var key in definition) {\n\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t\t}\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/**\n * _____ _ _\n * / ____| | | |\n * | | | |__ __ _ _ __ ___ ___| | ___ ___ _ __\n * | | | '_ \\ / _` | '_ ` _ \\ / _ \\ |/ _ \\/ _ \\| '_ \\\n * | |____| | | | (_| | | | | | | __/ | __/ (_) | | | |\n * \\_____|_| |_|\\__,_|_| |_| |_|\\___|_|\\___|\\___|_| |_|_\n * | _ \\ | | | |\n * | |_) | __ _ ___| | ____ _ _ __ ___ _ _ _ __ __| |___\n * | _ < / _` |/ __| |/ / _` | '__/ _ \\| | | | '_ \\ / _` / __|\n * | |_) | (_| | (__| < (_| | | | (_) | |_| | | | | (_| \\__ \\\n * |____/ \\__,_|\\___|_|\\_\\__, |_| \\___/ \\__,_|_| |_|\\__,_|___/\n * __/ |\n * |___/\n *\n * @module ChameleonBackgrounds\n * @version 2.1.2\n * @author Lennart van Ballegoij (https://weblenn.com/)\n * @license MIT\n * @see https://github.com/WebLenn/ChameleonBackgrounds\n *\n * A zero-dependency JavaScript library to dynamically load background\n * images with elegant fade-in transitions and slideshow support.\n */\n\n/**\n * @typedef {Object} ChameleonOptions\n * @property {string|HTMLElement} element - CSS selector or DOM element to attach to.\n * @property {'single'|'slider'} type - Background mode.\n * @property {string|string[]} src - Image URL(s). String for 'single', array for 'slider'.\n * @property {string} overlayColor - Overlay color (hex, rgb, rgba, hsl).\n * @property {string} [overlayImage] - Optional overlay pattern image URL.\n * @property {number} [minOverlay=0] - Minimum overlay opacity after fade (0–1).\n * @property {number} [transitionDuration=2000] - Fade duration in milliseconds.\n * @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.\n * @property {boolean} [sliderLoop=false] - Whether the slider restarts after the last slide.\n */\n\nclass ChameleonBackgrounds {\n /** @type {ChameleonOptions} */\n static #DEFAULTS = Object.freeze({\n element: 'body',\n type: 'single',\n src: '',\n overlayColor: '#0f1e25',\n overlayImage: null,\n minOverlay: 0,\n transitionDuration: 2000,\n sliderDuration: 8000,\n sliderLoop: false,\n });\n\n // Legacy snake_case → camelCase alias map\n static #ALIASES = Object.freeze({\n transition_duration: 'transitionDuration',\n slider_duration: 'sliderDuration',\n slider_loop: 'sliderLoop',\n min_overlay: 'minOverlay',\n overlay_color: 'overlayColor',\n overlay_image: 'overlayImage',\n });\n\n /** @type {ChameleonOptions} */\n #options;\n\n /** @type {HTMLElement} */\n #element;\n\n /** @type {string} */\n #uid;\n\n /** @type {string} */\n #originalHTML;\n\n /** @type {HTMLStyleElement|null} */\n #styleElement = null;\n\n /** @type {number|null} */\n #sliderIntervalId = null;\n\n /** @type {boolean} */\n #destroyed = false;\n\n /**\n * Create a new ChameleonBackgrounds instance.\n * @param {Partial<ChameleonOptions>} [options]\n */\n constructor(options = {}) {\n const normalized = ChameleonBackgrounds.#normalizeOptions(options);\n this.#options = { ...ChameleonBackgrounds.#DEFAULTS, ...normalized };\n this.#uid = ChameleonBackgrounds.#generateUID();\n this.#element = ChameleonBackgrounds.#resolveElement(this.#options.element);\n this.#originalHTML = this.#element.innerHTML;\n\n this.#init();\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /**\n * Clean up all injected DOM, styles, and intervals.\n * Restores the target element to its original state.\n */\n destroy() {\n if (this.#destroyed) return;\n this.#destroyed = true;\n\n // Stop any running slider\n if (this.#sliderIntervalId !== null) {\n clearInterval(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n\n // Remove injected style element\n if (this.#styleElement?.parentNode) {\n this.#styleElement.parentNode.removeChild(this.#styleElement);\n this.#styleElement = null;\n }\n\n // Unwrap the original content instead of resetting innerHTML\n const wrapper = this.#element.querySelector(`#cbg-inner-${this.#uid}`);\n if (wrapper && wrapper.parentNode === this.#element) {\n while (wrapper.firstChild) {\n this.#element.insertBefore(wrapper.firstChild, wrapper);\n }\n this.#element.removeChild(wrapper);\n }\n\n // Remove loader\n const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);\n if (loader) {\n loader.remove();\n }\n\n // Remove inline background-image\n this.#element.style.backgroundImage = '';\n }\n\n /**\n * The resolved options (read-only copy).\n * @returns {ChameleonOptions}\n */\n get options() {\n return { ...this.#options };\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n #init() {\n this.#injectStyles();\n this.#buildDOM();\n\n // If the element is NOT <body>, wait for window load to start loading images.\n // For <body>, the loader is already visible, so we start immediately to avoid\n // the re-execution issue the legacy lib had with body scripts.\n if (this.#element.matches('body')) {\n // Use a microtask so the DOM changes above settle first.\n queueMicrotask(() => this.#retrieveBackground());\n } else {\n if (document.readyState === 'complete') {\n this.#retrieveBackground();\n } else {\n window.addEventListener('load', () => this.#retrieveBackground(), { once: true });\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // DOM Construction\n // ---------------------------------------------------------------------------\n\n /**\n * Inject a scoped <style> element into the <head>.\n */\n #injectStyles() {\n const uid = this.#uid;\n const selector = this.#options.element === 'body' || this.#element.matches('body')\n ? 'body'\n : this.#options.element;\n const duration = this.#options.transitionDuration / 1000; // ms → s\n const position = this.#element.matches('body') ? 'fixed' : 'absolute';\n const overlayBg = this.#options.overlayImage\n ? `url(${this.#options.overlayImage})`\n : 'none';\n\n const css = `\n ${typeof selector === 'string' ? selector : `.cbg-host-${uid}`} {\n position: relative;\n }\n\n #cbg-inner-${uid} {\n z-index: 2;\n position: relative;\n }\n\n .cbg-loader-${uid} {\n height: 100%;\n width: 100%;\n position: ${position};\n background-image: ${overlayBg};\n background-color: ${this.#options.overlayColor};\n opacity: 1;\n z-index: 1;\n transition: opacity ${duration}s ease;\n top: 0;\n left: 0;\n }\n `;\n\n const style = document.createElement('style');\n style.dataset.chameleonUid = uid;\n style.textContent = css;\n document.head.appendChild(style);\n this.#styleElement = style;\n }\n\n /**\n * Wrap existing content and insert the overlay loader.\n */\n #buildDOM() {\n const uid = this.#uid;\n\n const wrapper = document.createElement('div');\n wrapper.id = `cbg-inner-${uid}`;\n\n // Move all child nodes from the element into the wrapper\n while (this.#element.firstChild) {\n wrapper.appendChild(this.#element.firstChild);\n }\n\n const loader = document.createElement('div');\n loader.classList.add(`cbg-loader-${uid}`);\n\n // Append wrapper and loader\n this.#element.appendChild(wrapper);\n this.#element.appendChild(loader);\n }\n\n // ---------------------------------------------------------------------------\n // Background Loading\n // ---------------------------------------------------------------------------\n\n /**\n * Route to the correct loader based on type.\n */\n #retrieveBackground() {\n if (this.#destroyed) return;\n\n if (this.#options.type === 'single') {\n this.#loadBackground(typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0]);\n } else if (this.#options.type === 'slider') {\n this.#startSlider();\n }\n }\n\n /**\n * Preload an image and apply it as the element's background.\n * @param {string} src - Image URL to load.\n * @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).\n * @returns {Promise<void>}\n */\n #loadBackground(src, isFirst = true) {\n if (this.#destroyed) return Promise.resolve();\n\n return new Promise((resolve) => {\n const img = new Image();\n\n img.onload = () => {\n if (this.#destroyed) return resolve();\n\n this.#element.style.backgroundImage = `url(${src})`;\n this.#element.style.backgroundSize = 'cover';\n\n const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);\n if (loader) {\n loader.style.opacity = String(this.#options.minOverlay);\n }\n\n resolve();\n };\n\n img.onerror = () => {\n console.warn(`[ChameleonBackgrounds] Failed to load image: ${src}`);\n resolve();\n };\n\n img.src = src;\n });\n }\n\n /**\n /**\n * Reload background with a fade-out → swap → fade-in cycle.\n * @param {string} [src] - Optional new image URL. If omitted, reloads using current options.\n * @returns {Promise<void>}\n */\n reloadBackground(src) {\n if (this.#destroyed) return Promise.resolve();\n\n if (src !== undefined) {\n if (this.#options.type === 'slider' && typeof src === 'string') {\n // If the user passes a comma-separated string, split it. Otherwise wrap it.\n this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];\n } else {\n this.#options.src = src;\n }\n }\n\n // Stop any running slider\n if (this.#sliderIntervalId !== null) {\n clearInterval(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n\n const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);\n if (loader) {\n loader.style.opacity = '1';\n }\n\n return new Promise((resolve) => {\n setTimeout(() => {\n if (this.#destroyed) return resolve();\n\n if (this.#options.type === 'single') {\n const singleSrc = typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0];\n this.#loadBackground(singleSrc, false).then(resolve);\n } else if (this.#options.type === 'slider') {\n this.#startSlider();\n resolve();\n }\n }, this.#options.transitionDuration);\n });\n }\n\n /**\n * Update options on the fly without destroying the instance.\n * @param {Object} newOptions - The new options to merge.\n */\n reloadOptions(newOptions) {\n if (this.#destroyed) return;\n\n const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);\n this.#options = { ...this.#options, ...normalized };\n\n // Remove old styles\n if (this.#styleElement?.parentNode) {\n this.#styleElement.parentNode.removeChild(this.#styleElement);\n this.#styleElement = null;\n }\n\n // Inject updated styles\n this.#injectStyles();\n }\n\n // ---------------------------------------------------------------------------\n // Slider\n // ---------------------------------------------------------------------------\n\n /**\n * Start the background slideshow.\n */\n #startSlider() {\n if (this.#destroyed) return;\n\n const sources = this.#options.src;\n if (!Array.isArray(sources) || sources.length === 0) {\n console.warn('[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.');\n return;\n }\n\n // Load the first slide immediately\n let index = 0;\n this.#loadBackground(sources[index]).then(() => {\n if (this.#destroyed) return;\n\n index = 1;\n if (sources.length === 1) return; // only one slide, nothing to rotate\n\n const interval = this.#options.sliderDuration + this.#options.transitionDuration * 2;\n\n this.#sliderIntervalId = setInterval(() => {\n if (this.#destroyed) {\n clearInterval(this.#sliderIntervalId);\n return;\n }\n\n this.#cycleSliderSlide(sources[index]);\n index++;\n\n if (index >= sources.length) {\n if (this.#options.sliderLoop) {\n index = 0;\n } else {\n clearInterval(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n }\n }, interval);\n });\n }\n\n /**\n * Cycle to a specific slide without resetting the slider instance.\n * @param {string} src - The image URL to load.\n */\n #cycleSliderSlide(src) {\n if (this.#destroyed) return;\n\n const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);\n if (loader) {\n loader.style.opacity = '1';\n }\n\n setTimeout(() => {\n if (this.#destroyed) return;\n this.#loadBackground(src, false);\n }, this.#options.transitionDuration);\n }\n\n // ---------------------------------------------------------------------------\n // Static Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Convert legacy snake_case option keys to camelCase.\n * @param {Object} opts\n * @returns {Object}\n */\n static #normalizeOptions(opts) {\n const result = {};\n for (const [key, value] of Object.entries(opts)) {\n const alias = ChameleonBackgrounds.#ALIASES[key];\n result[alias ?? key] = value;\n }\n return result;\n }\n\n /**\n * Resolve a CSS selector string or HTMLElement to an HTMLElement.\n * @param {string|HTMLElement} el\n * @returns {HTMLElement}\n */\n static #resolveElement(el) {\n if (el instanceof HTMLElement) return el;\n if (typeof el === 'string') {\n if (el === 'body') return document.body;\n const found = document.querySelector(el);\n if (!found) throw new Error(`[ChameleonBackgrounds] Element not found: ${el}`);\n return /** @type {HTMLElement} */ (found);\n }\n throw new TypeError(`[ChameleonBackgrounds] Invalid element: ${el}`);\n }\n\n /**\n * Generate a short unique ID for scoping styles.\n * @returns {string}\n */\n static #generateUID() {\n // Prefer crypto.randomUUID where available, otherwise fall back\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID().slice(0, 8);\n }\n // Fallback: random alphanumeric string\n return Array.from({ length: 8 }, () =>\n 'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))\n ).join('');\n }\n}\n\nexport default ChameleonBackgrounds;\nexport { ChameleonBackgrounds };\n"],"names":[],"sourceRoot":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! For license information please see chameleon-backgrounds.min.js.LICENSE.txt */
|
|
2
|
-
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ChameleonBackgrounds=t():e.ChameleonBackgrounds=t()}(this,()=>(()=>{"use strict";const e={d:(t,i)=>{if(Array.isArray(i))for(var n=0;n<i.length;){var o=i[n++],r=i[n++];e.o(t,o)?0===r&&n++:0===r?Object.defineProperty(t,o,{enumerable:!0,value:i[n++]}):Object.defineProperty(t,o,{enumerable:!0,get:r})}else for(var o in i)e.o(i,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:i[o]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};let t={};class i{static#e=Object.freeze({element:"body",type:"single",src:"",overlayColor:"#0f1e25",overlayImage:null,minOverlay:0,transitionDuration:2e3,sliderDuration:8e3,sliderLoop:!1});static#t=Object.freeze({transition_duration:"transitionDuration",slider_duration:"sliderDuration",slider_loop:"sliderLoop",min_overlay:"minOverlay",overlay_color:"overlayColor",overlay_image:"overlayImage"});#i;#n;#o;#r;#s=null;#l=null;#d=!1;constructor(e={}){const t=i.#a(e);this.#i={...i.#e,...t},this.#o=i.#h(),this.#n=i.#c(this.#i.element),this.#r=this.#n.innerHTML,this.#u()}destroy(){if(this.#d)return;this.#d=!0,null!==this.#l&&(clearInterval(this.#l),this.#l=null),this.#s?.parentNode&&(this.#s.parentNode.removeChild(this.#s),this.#s=null);const e=this.#n.querySelector(`#cbg-inner-${this.#o}`);if(e&&e.parentNode===this.#n){for(;e.firstChild;)this.#n.insertBefore(e.firstChild,e);this.#n.removeChild(e)}const t=this.#n.querySelector(`.cbg-loader-${this.#o}`);t&&t.remove(),this.#n.style.backgroundImage=""}get options(){return{...this.#i}}#u(){this.#m(),this.#
|
|
2
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ChameleonBackgrounds=t():e.ChameleonBackgrounds=t()}(this,()=>(()=>{"use strict";const e={d:(t,i)=>{if(Array.isArray(i))for(var n=0;n<i.length;){var o=i[n++],r=i[n++];e.o(t,o)?0===r&&n++:0===r?Object.defineProperty(t,o,{enumerable:!0,value:i[n++]}):Object.defineProperty(t,o,{enumerable:!0,get:r})}else for(var o in i)e.o(i,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:i[o]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};let t={};class i{static#e=Object.freeze({element:"body",type:"single",src:"",overlayColor:"#0f1e25",overlayImage:null,minOverlay:0,transitionDuration:2e3,sliderDuration:8e3,sliderLoop:!1});static#t=Object.freeze({transition_duration:"transitionDuration",slider_duration:"sliderDuration",slider_loop:"sliderLoop",min_overlay:"minOverlay",overlay_color:"overlayColor",overlay_image:"overlayImage"});#i;#n;#o;#r;#s=null;#l=null;#d=!1;constructor(e={}){const t=i.#a(e);this.#i={...i.#e,...t},this.#o=i.#h(),this.#n=i.#c(this.#i.element),this.#r=this.#n.innerHTML,this.#u()}destroy(){if(this.#d)return;this.#d=!0,null!==this.#l&&(clearInterval(this.#l),this.#l=null),this.#s?.parentNode&&(this.#s.parentNode.removeChild(this.#s),this.#s=null);const e=this.#n.querySelector(`#cbg-inner-${this.#o}`);if(e&&e.parentNode===this.#n){for(;e.firstChild;)this.#n.insertBefore(e.firstChild,e);this.#n.removeChild(e)}const t=this.#n.querySelector(`.cbg-loader-${this.#o}`);t&&t.remove(),this.#n.style.backgroundImage=""}get options(){return{...this.#i}}#u(){this.#m(),this.#y(),this.#n.matches("body")?queueMicrotask(()=>this.#p()):"complete"===document.readyState?this.#p():window.addEventListener("load",()=>this.#p(),{once:!0})}#m(){const e=this.#o,t="body"===this.#i.element||this.#n.matches("body")?"body":this.#i.element,i=this.#i.transitionDuration/1e3,n=`\n ${"string"==typeof t?t:`.cbg-host-${e}`} {\n position: relative;\n }\n\n #cbg-inner-${e} {\n z-index: 2;\n position: relative;\n }\n\n .cbg-loader-${e} {\n height: 100%;\n width: 100%;\n position: ${this.#n.matches("body")?"fixed":"absolute"};\n background-image: ${this.#i.overlayImage?`url(${this.#i.overlayImage})`:"none"};\n background-color: ${this.#i.overlayColor};\n opacity: 1;\n z-index: 1;\n transition: opacity ${i}s ease;\n top: 0;\n left: 0;\n }\n `,o=document.createElement("style");o.dataset.chameleonUid=e,o.textContent=n,document.head.appendChild(o),this.#s=o}#y(){const e=this.#o,t=document.createElement("div");for(t.id=`cbg-inner-${e}`;this.#n.firstChild;)t.appendChild(this.#n.firstChild);const i=document.createElement("div");i.classList.add(`cbg-loader-${e}`),this.#n.appendChild(t),this.#n.appendChild(i)}#p(){this.#d||("single"===this.#i.type?this.#g("string"==typeof this.#i.src?this.#i.src:this.#i.src[0]):"slider"===this.#i.type&&this.#f())}#g(e,t=!0){return this.#d?Promise.resolve():new Promise(t=>{const i=new Image;i.onload=()=>{if(this.#d)return t();this.#n.style.backgroundImage=`url(${e})`,this.#n.style.backgroundSize="cover";const i=this.#n.querySelector(`.cbg-loader-${this.#o}`);i&&(i.style.opacity=String(this.#i.minOverlay)),t()},i.onerror=()=>{console.warn(`[ChameleonBackgrounds] Failed to load image: ${e}`),t()},i.src=e})}reloadBackground(e){if(this.#d)return Promise.resolve();void 0!==e&&("slider"===this.#i.type&&"string"==typeof e?this.#i.src=e.includes(",")?e.split(",").map(e=>e.trim()):[e]:this.#i.src=e),null!==this.#l&&(clearInterval(this.#l),this.#l=null);const t=this.#n.querySelector(`.cbg-loader-${this.#o}`);return t&&(t.style.opacity="1"),new Promise(e=>{setTimeout(()=>{if(this.#d)return e();if("single"===this.#i.type){const t="string"==typeof this.#i.src?this.#i.src:this.#i.src[0];this.#g(t,!1).then(e)}else"slider"===this.#i.type&&(this.#f(),e())},this.#i.transitionDuration)})}reloadOptions(e){if(this.#d)return;const t=i.#a(e);this.#i={...this.#i,...t},this.#s?.parentNode&&(this.#s.parentNode.removeChild(this.#s),this.#s=null),this.#m()}#f(){if(this.#d)return;const e=this.#i.src;if(!Array.isArray(e)||0===e.length)return void console.warn("[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.");let t=0;this.#g(e[t]).then(()=>{if(this.#d)return;if(t=1,1===e.length)return;const i=this.#i.sliderDuration+2*this.#i.transitionDuration;this.#l=setInterval(()=>{this.#d?clearInterval(this.#l):(this.#v(e[t]),t++,t>=e.length&&(this.#i.sliderLoop?t=0:(clearInterval(this.#l),this.#l=null)))},i)})}#v(e){if(this.#d)return;const t=this.#n.querySelector(`.cbg-loader-${this.#o}`);t&&(t.style.opacity="1"),setTimeout(()=>{this.#d||this.#g(e,!1)},this.#i.transitionDuration)}static#a(e){const t={};for(const[n,o]of Object.entries(e))t[i.#t[n]??n]=o;return t}static#c(e){if(e instanceof HTMLElement)return e;if("string"==typeof e){if("body"===e)return document.body;const t=document.querySelector(e);if(!t)throw new Error(`[ChameleonBackgrounds] Element not found: ${e}`);return t}throw new TypeError(`[ChameleonBackgrounds] Invalid element: ${e}`)}static#h(){return"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID().slice(0,8):Array.from({length:8},()=>"abcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(36*Math.random()))).join("")}}const n=i;return e.d(t,["default",0,n]),t=t.default,t})());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chameleon-backgrounds",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "A zero-dependency JavaScript library to dynamically load background images with elegant fade-in transitions and slideshow support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/chameleon-backgrounds.js",
|
|
@@ -51,4 +51,4 @@
|
|
|
51
51
|
"webpack": "^5.109.0",
|
|
52
52
|
"webpack-cli": "^7.2.1"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* |___/
|
|
15
15
|
*
|
|
16
16
|
* @module ChameleonBackgrounds
|
|
17
|
-
* @version 2.
|
|
17
|
+
* @version 2.1.2
|
|
18
18
|
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
19
19
|
* @license MIT
|
|
20
20
|
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
@@ -324,7 +324,7 @@ class ChameleonBackgrounds {
|
|
|
324
324
|
return new Promise((resolve) => {
|
|
325
325
|
setTimeout(() => {
|
|
326
326
|
if (this.#destroyed) return resolve();
|
|
327
|
-
|
|
327
|
+
|
|
328
328
|
if (this.#options.type === 'single') {
|
|
329
329
|
const singleSrc = typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0];
|
|
330
330
|
this.#loadBackground(singleSrc, false).then(resolve);
|
|
@@ -388,7 +388,7 @@ class ChameleonBackgrounds {
|
|
|
388
388
|
return;
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
this
|
|
391
|
+
this.#cycleSliderSlide(sources[index]);
|
|
392
392
|
index++;
|
|
393
393
|
|
|
394
394
|
if (index >= sources.length) {
|
|
@@ -403,6 +403,24 @@ class ChameleonBackgrounds {
|
|
|
403
403
|
});
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Cycle to a specific slide without resetting the slider instance.
|
|
408
|
+
* @param {string} src - The image URL to load.
|
|
409
|
+
*/
|
|
410
|
+
#cycleSliderSlide(src) {
|
|
411
|
+
if (this.#destroyed) return;
|
|
412
|
+
|
|
413
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
414
|
+
if (loader) {
|
|
415
|
+
loader.style.opacity = '1';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
setTimeout(() => {
|
|
419
|
+
if (this.#destroyed) return;
|
|
420
|
+
this.#loadBackground(src, false);
|
|
421
|
+
}, this.#options.transitionDuration);
|
|
422
|
+
}
|
|
423
|
+
|
|
406
424
|
// ---------------------------------------------------------------------------
|
|
407
425
|
// Static Helpers
|
|
408
426
|
// ---------------------------------------------------------------------------
|