chameleon-backgrounds 2.1.1 → 2.1.3
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
CHANGED
|
@@ -127,6 +127,7 @@ bg.destroy();
|
|
|
127
127
|
| `transitionDuration` | `number` | `2000` | yes | Fade duration in milliseconds |
|
|
128
128
|
| `sliderDuration` | `number` | `8000` | slider only | Time each slide is shown (ms) |
|
|
129
129
|
| `sliderLoop` | `boolean` | `false` | slider only | Restart slider after last slide |
|
|
130
|
+
| `fetchPriority` | `string` | `'auto'` | no | Network priority for the initial image load. Set to `'high'` for LCP optimization. |
|
|
130
131
|
|
|
131
132
|
### Legacy Option Names
|
|
132
133
|
|
|
@@ -143,6 +144,18 @@ For backward compatibility, v1 snake_case option names are still supported:
|
|
|
143
144
|
|
|
144
145
|
---
|
|
145
146
|
|
|
147
|
+
## Optimize for Largest Contentful Paint (LCP)
|
|
148
|
+
|
|
149
|
+
To get a great LCP score on PageSpeed Insights, you should set `fetchPriority: 'high'` on your main above-the-fold background instance.
|
|
150
|
+
|
|
151
|
+
Additionally, because browsers cannot discover JavaScript-loaded images in the initial HTML document parse, you should add a preload link to your `<head>` for the hero image:
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<link rel="preload" as="image" href="path/to/hero.jpg" fetchpriority="high">
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
146
159
|
## API
|
|
147
160
|
|
|
148
161
|
### `new ChameleonBackgrounds(options)`
|
|
@@ -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,33 @@
|
|
|
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.3
|
|
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
|
+
* @property {'high'|'low'|'auto'} [fetchPriority='auto'] - fetchPriority for the first loaded image (e.g., 'high' for LCP images).
|
|
107
|
+
*/
|
|
50
108
|
|
|
51
|
-
|
|
109
|
+
class ChameleonBackgrounds {
|
|
110
|
+
/** @type {ChameleonOptions} */
|
|
111
|
+
static #DEFAULTS = Object.freeze({
|
|
52
112
|
element: 'body',
|
|
53
113
|
type: 'single',
|
|
54
114
|
src: '',
|
|
@@ -58,299 +118,440 @@
|
|
|
58
118
|
transitionDuration: 2000,
|
|
59
119
|
sliderDuration: 8000,
|
|
60
120
|
sliderLoop: false,
|
|
121
|
+
fetchPriority: 'auto',
|
|
61
122
|
});
|
|
62
123
|
|
|
63
|
-
|
|
124
|
+
// Legacy snake_case → camelCase alias map
|
|
125
|
+
static #ALIASES = Object.freeze({
|
|
64
126
|
transition_duration: 'transitionDuration',
|
|
65
127
|
slider_duration: 'sliderDuration',
|
|
66
128
|
slider_loop: 'sliderLoop',
|
|
67
129
|
min_overlay: 'minOverlay',
|
|
68
130
|
overlay_color: 'overlayColor',
|
|
69
131
|
overlay_image: 'overlayImage',
|
|
132
|
+
fetch_priority: 'fetchPriority',
|
|
70
133
|
});
|
|
71
134
|
|
|
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
|
-
}
|
|
135
|
+
/** @type {ChameleonOptions} */
|
|
136
|
+
#options;
|
|
83
137
|
|
|
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
|
-
}
|
|
138
|
+
/** @type {HTMLElement} */
|
|
139
|
+
#element;
|
|
97
140
|
|
|
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
|
-
}
|
|
141
|
+
/** @type {string} */
|
|
142
|
+
#uid;
|
|
109
143
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// -------------------------------------------------------------------------
|
|
144
|
+
/** @type {string} */
|
|
145
|
+
#originalHTML;
|
|
113
146
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return new ChameleonBackgrounds(options);
|
|
117
|
-
}
|
|
147
|
+
/** @type {HTMLStyleElement|null} */
|
|
148
|
+
#styleElement = null;
|
|
118
149
|
|
|
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
|
-
}
|
|
150
|
+
/** @type {number|null} */
|
|
151
|
+
#sliderIntervalId = null;
|
|
126
152
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this._originalHTML = this._element.innerHTML;
|
|
130
|
-
this._styleElement = null;
|
|
131
|
-
this._sliderIntervalId = null;
|
|
132
|
-
this._destroyed = false;
|
|
153
|
+
/** @type {boolean} */
|
|
154
|
+
#destroyed = false;
|
|
133
155
|
|
|
134
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Create a new ChameleonBackgrounds instance.
|
|
158
|
+
* @param {Partial<ChameleonOptions>} [options]
|
|
159
|
+
*/
|
|
160
|
+
constructor(options = {}) {
|
|
161
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(options);
|
|
162
|
+
this.#options = { ...ChameleonBackgrounds.#DEFAULTS, ...normalized };
|
|
163
|
+
this.#uid = ChameleonBackgrounds.#generateUID();
|
|
164
|
+
this.#element = ChameleonBackgrounds.#resolveElement(this.#options.element);
|
|
165
|
+
this.#originalHTML = this.#element.innerHTML;
|
|
166
|
+
|
|
167
|
+
this.#init();
|
|
135
168
|
}
|
|
136
169
|
|
|
137
|
-
//
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
138
171
|
// Public API
|
|
139
|
-
//
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
140
173
|
|
|
141
174
|
/**
|
|
142
175
|
* Clean up all injected DOM, styles, and intervals.
|
|
143
176
|
* Restores the target element to its original state.
|
|
144
177
|
*/
|
|
145
|
-
|
|
146
|
-
if (this
|
|
147
|
-
this
|
|
178
|
+
destroy() {
|
|
179
|
+
if (this.#destroyed) return;
|
|
180
|
+
this.#destroyed = true;
|
|
181
|
+
|
|
182
|
+
// Stop any running slider
|
|
183
|
+
if (this.#sliderIntervalId !== null) {
|
|
184
|
+
clearInterval(this.#sliderIntervalId);
|
|
185
|
+
this.#sliderIntervalId = null;
|
|
186
|
+
}
|
|
148
187
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.
|
|
188
|
+
// Remove injected style element
|
|
189
|
+
if (this.#styleElement?.parentNode) {
|
|
190
|
+
this.#styleElement.parentNode.removeChild(this.#styleElement);
|
|
191
|
+
this.#styleElement = null;
|
|
152
192
|
}
|
|
153
193
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
194
|
+
// Unwrap the original content instead of resetting innerHTML
|
|
195
|
+
const wrapper = this.#element.querySelector(`#cbg-inner-${this.#uid}`);
|
|
196
|
+
if (wrapper && wrapper.parentNode === this.#element) {
|
|
197
|
+
while (wrapper.firstChild) {
|
|
198
|
+
this.#element.insertBefore(wrapper.firstChild, wrapper);
|
|
199
|
+
}
|
|
200
|
+
this.#element.removeChild(wrapper);
|
|
157
201
|
}
|
|
158
202
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
203
|
+
// Remove loader
|
|
204
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
205
|
+
if (loader) {
|
|
206
|
+
loader.remove();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Remove inline background-image
|
|
210
|
+
this.#element.style.backgroundImage = '';
|
|
211
|
+
}
|
|
162
212
|
|
|
163
213
|
/**
|
|
164
|
-
*
|
|
214
|
+
* The resolved options (read-only copy).
|
|
215
|
+
* @returns {ChameleonOptions}
|
|
165
216
|
*/
|
|
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
|
-
// -------------------------------------------------------------------------
|
|
217
|
+
get options() {
|
|
218
|
+
return { ...this.#options };
|
|
219
|
+
}
|
|
179
220
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Initialization
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
183
224
|
|
|
184
|
-
|
|
225
|
+
#init() {
|
|
226
|
+
this.#injectStyles();
|
|
227
|
+
this.#buildDOM();
|
|
185
228
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
229
|
+
// If the element is NOT <body>, wait for window load to start loading images.
|
|
230
|
+
// For <body>, the loader is already visible, so we start immediately to avoid
|
|
231
|
+
// the re-execution issue the legacy lib had with body scripts.
|
|
232
|
+
if (this.#element.matches('body')) {
|
|
233
|
+
// Use a microtask so the DOM changes above settle first.
|
|
234
|
+
queueMicrotask(() => this.#retrieveBackground());
|
|
189
235
|
} else {
|
|
190
236
|
if (document.readyState === 'complete') {
|
|
191
|
-
this
|
|
237
|
+
this.#retrieveBackground();
|
|
192
238
|
} else {
|
|
193
|
-
window.addEventListener('load',
|
|
194
|
-
window.removeEventListener('load', onLoad);
|
|
195
|
-
self._retrieveBackground();
|
|
196
|
-
});
|
|
239
|
+
window.addEventListener('load', () => this.#retrieveBackground(), { once: true });
|
|
197
240
|
}
|
|
198
241
|
}
|
|
199
|
-
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// DOM Construction
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
200
247
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Inject a scoped <style> element into the <head>.
|
|
250
|
+
*/
|
|
251
|
+
#injectStyles() {
|
|
252
|
+
const uid = this.#uid;
|
|
253
|
+
const selector = this.#options.element === 'body' || this.#element.matches('body')
|
|
204
254
|
? 'body'
|
|
205
|
-
: this.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
?
|
|
255
|
+
: this.#options.element;
|
|
256
|
+
const duration = this.#options.transitionDuration / 1000; // ms → s
|
|
257
|
+
const position = this.#element.matches('body') ? 'fixed' : 'absolute';
|
|
258
|
+
const overlayBg = this.#options.overlayImage
|
|
259
|
+
? `url(${this.#options.overlayImage})`
|
|
210
260
|
: 'none';
|
|
211
261
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
262
|
+
const css = `
|
|
263
|
+
${typeof selector === 'string' ? selector : `.cbg-host-${uid}`} {
|
|
264
|
+
position: relative;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#cbg-inner-${uid} {
|
|
268
|
+
z-index: 2;
|
|
269
|
+
position: relative;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.cbg-loader-${uid} {
|
|
273
|
+
height: 100%;
|
|
274
|
+
width: 100%;
|
|
275
|
+
position: ${position};
|
|
276
|
+
background-image: ${overlayBg};
|
|
277
|
+
background-color: ${this.#options.overlayColor};
|
|
278
|
+
opacity: 1;
|
|
279
|
+
z-index: 1;
|
|
280
|
+
transition: opacity ${duration}s ease;
|
|
281
|
+
top: 0;
|
|
282
|
+
left: 0;
|
|
283
|
+
}
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
const style = document.createElement('style');
|
|
287
|
+
style.dataset.chameleonUid = uid;
|
|
229
288
|
style.textContent = css;
|
|
230
289
|
document.head.appendChild(style);
|
|
231
|
-
this
|
|
232
|
-
}
|
|
290
|
+
this.#styleElement = style;
|
|
291
|
+
}
|
|
233
292
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Wrap existing content and insert the overlay loader.
|
|
295
|
+
*/
|
|
296
|
+
#buildDOM() {
|
|
297
|
+
const uid = this.#uid;
|
|
237
298
|
|
|
238
|
-
|
|
239
|
-
wrapper.id =
|
|
240
|
-
wrapper.innerHTML = content;
|
|
299
|
+
const wrapper = document.createElement('div');
|
|
300
|
+
wrapper.id = `cbg-inner-${uid}`;
|
|
241
301
|
|
|
242
|
-
|
|
243
|
-
|
|
302
|
+
// Move all child nodes from the element into the wrapper
|
|
303
|
+
while (this.#element.firstChild) {
|
|
304
|
+
wrapper.appendChild(this.#element.firstChild);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const loader = document.createElement('div');
|
|
308
|
+
loader.classList.add(`cbg-loader-${uid}`);
|
|
244
309
|
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
this.
|
|
248
|
-
}
|
|
310
|
+
// Append wrapper and loader
|
|
311
|
+
this.#element.appendChild(wrapper);
|
|
312
|
+
this.#element.appendChild(loader);
|
|
313
|
+
}
|
|
249
314
|
|
|
250
|
-
//
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
251
316
|
// Background Loading
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.
|
|
262
|
-
} else if (this.
|
|
263
|
-
this
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Route to the correct loader based on type.
|
|
321
|
+
*/
|
|
322
|
+
#retrieveBackground() {
|
|
323
|
+
if (this.#destroyed) return;
|
|
324
|
+
|
|
325
|
+
if (this.#options.type === 'single') {
|
|
326
|
+
this.#loadBackground(typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0]);
|
|
327
|
+
} else if (this.#options.type === 'slider') {
|
|
328
|
+
this.#startSlider();
|
|
264
329
|
}
|
|
265
|
-
}
|
|
330
|
+
}
|
|
266
331
|
|
|
267
|
-
|
|
268
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Preload an image and apply it as the element's background.
|
|
334
|
+
* @param {string} src - Image URL to load.
|
|
335
|
+
* @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).
|
|
336
|
+
* @returns {Promise<void>}
|
|
337
|
+
*/
|
|
338
|
+
#loadBackground(src, isFirst = true) {
|
|
339
|
+
if (this.#destroyed) return Promise.resolve();
|
|
269
340
|
|
|
270
|
-
|
|
271
|
-
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
const img = new Image();
|
|
343
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
344
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
345
|
+
}
|
|
272
346
|
|
|
273
|
-
|
|
274
|
-
|
|
347
|
+
img.onload = () => {
|
|
348
|
+
if (this.#destroyed) return resolve();
|
|
275
349
|
|
|
276
|
-
|
|
277
|
-
|
|
350
|
+
this.#element.style.backgroundImage = `url(${src})`;
|
|
351
|
+
this.#element.style.backgroundSize = 'cover';
|
|
278
352
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
353
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
354
|
+
if (loader) {
|
|
355
|
+
loader.style.opacity = String(this.#options.minOverlay);
|
|
356
|
+
}
|
|
283
357
|
|
|
284
|
-
|
|
285
|
-
|
|
358
|
+
resolve();
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
img.onerror = () => {
|
|
362
|
+
console.warn(`[ChameleonBackgrounds] Failed to load image: ${src}`);
|
|
363
|
+
resolve();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
img.src = src;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
286
369
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
370
|
+
/**
|
|
371
|
+
/**
|
|
372
|
+
* Reload background with a fade-out → swap → fade-in cycle.
|
|
373
|
+
* @param {string} [src] - Optional new image URL. If omitted, reloads using current options.
|
|
374
|
+
* @returns {Promise<void>}
|
|
375
|
+
*/
|
|
376
|
+
reloadBackground(src) {
|
|
377
|
+
if (this.#destroyed) return Promise.resolve();
|
|
291
378
|
|
|
292
|
-
|
|
293
|
-
|
|
379
|
+
if (src !== undefined) {
|
|
380
|
+
if (this.#options.type === 'slider' && typeof src === 'string') {
|
|
381
|
+
// If the user passes a comma-separated string, split it. Otherwise wrap it.
|
|
382
|
+
this.#options.src = src.includes(',') ? src.split(',').map(s => s.trim()) : [src];
|
|
383
|
+
} else {
|
|
384
|
+
this.#options.src = src;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
294
387
|
|
|
295
|
-
|
|
296
|
-
if (this
|
|
388
|
+
// Stop any running slider
|
|
389
|
+
if (this.#sliderIntervalId !== null) {
|
|
390
|
+
clearInterval(this.#sliderIntervalId);
|
|
391
|
+
this.#sliderIntervalId = null;
|
|
392
|
+
}
|
|
297
393
|
|
|
298
|
-
|
|
394
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
299
395
|
if (loader) {
|
|
300
396
|
loader.style.opacity = '1';
|
|
301
397
|
}
|
|
302
398
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self._loadBackground(src, callback);
|
|
307
|
-
}, this._options.transitionDuration);
|
|
308
|
-
};
|
|
399
|
+
return new Promise((resolve) => {
|
|
400
|
+
setTimeout(() => {
|
|
401
|
+
if (this.#destroyed) return resolve();
|
|
309
402
|
|
|
310
|
-
|
|
403
|
+
if (this.#options.type === 'single') {
|
|
404
|
+
const singleSrc = typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0];
|
|
405
|
+
this.#loadBackground(singleSrc, false).then(resolve);
|
|
406
|
+
} else if (this.#options.type === 'slider') {
|
|
407
|
+
this.#startSlider();
|
|
408
|
+
resolve();
|
|
409
|
+
}
|
|
410
|
+
}, this.#options.transitionDuration);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Update options on the fly without destroying the instance.
|
|
416
|
+
* @param {Object} newOptions - The new options to merge.
|
|
417
|
+
*/
|
|
418
|
+
reloadOptions(newOptions) {
|
|
419
|
+
if (this.#destroyed) return;
|
|
420
|
+
|
|
421
|
+
const normalized = ChameleonBackgrounds.#normalizeOptions(newOptions);
|
|
422
|
+
this.#options = { ...this.#options, ...normalized };
|
|
423
|
+
|
|
424
|
+
// Remove old styles
|
|
425
|
+
if (this.#styleElement?.parentNode) {
|
|
426
|
+
this.#styleElement.parentNode.removeChild(this.#styleElement);
|
|
427
|
+
this.#styleElement = null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Inject updated styles
|
|
431
|
+
this.#injectStyles();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
311
435
|
// Slider
|
|
312
|
-
//
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
313
437
|
|
|
314
|
-
|
|
315
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Start the background slideshow.
|
|
440
|
+
*/
|
|
441
|
+
#startSlider() {
|
|
442
|
+
if (this.#destroyed) return;
|
|
316
443
|
|
|
317
|
-
|
|
444
|
+
const sources = this.#options.src;
|
|
318
445
|
if (!Array.isArray(sources) || sources.length === 0) {
|
|
319
446
|
console.warn('[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.');
|
|
320
447
|
return;
|
|
321
448
|
}
|
|
322
449
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (self._destroyed) return;
|
|
450
|
+
// Load the first slide immediately
|
|
451
|
+
let index = 0;
|
|
452
|
+
this.#loadBackground(sources[index]).then(() => {
|
|
453
|
+
if (this.#destroyed) return;
|
|
328
454
|
|
|
329
455
|
index = 1;
|
|
330
|
-
if (sources.length === 1) return;
|
|
456
|
+
if (sources.length === 1) return; // only one slide, nothing to rotate
|
|
331
457
|
|
|
332
|
-
|
|
458
|
+
const interval = this.#options.sliderDuration + this.#options.transitionDuration * 2;
|
|
333
459
|
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
clearInterval(
|
|
460
|
+
this.#sliderIntervalId = setInterval(() => {
|
|
461
|
+
if (this.#destroyed) {
|
|
462
|
+
clearInterval(this.#sliderIntervalId);
|
|
337
463
|
return;
|
|
338
464
|
}
|
|
339
465
|
|
|
340
|
-
|
|
466
|
+
this.#cycleSliderSlide(sources[index]);
|
|
341
467
|
index++;
|
|
342
468
|
|
|
343
469
|
if (index >= sources.length) {
|
|
344
|
-
if (
|
|
470
|
+
if (this.#options.sliderLoop) {
|
|
345
471
|
index = 0;
|
|
346
472
|
} else {
|
|
347
|
-
clearInterval(
|
|
348
|
-
|
|
473
|
+
clearInterval(this.#sliderIntervalId);
|
|
474
|
+
this.#sliderIntervalId = null;
|
|
349
475
|
}
|
|
350
476
|
}
|
|
351
477
|
}, interval);
|
|
352
478
|
});
|
|
353
|
-
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Cycle to a specific slide without resetting the slider instance.
|
|
483
|
+
* @param {string} src - The image URL to load.
|
|
484
|
+
*/
|
|
485
|
+
#cycleSliderSlide(src) {
|
|
486
|
+
if (this.#destroyed) return;
|
|
487
|
+
|
|
488
|
+
const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);
|
|
489
|
+
if (loader) {
|
|
490
|
+
loader.style.opacity = '1';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
if (this.#destroyed) return;
|
|
495
|
+
this.#loadBackground(src, false);
|
|
496
|
+
}, this.#options.transitionDuration);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
// Static Helpers
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Convert legacy snake_case option keys to camelCase.
|
|
505
|
+
* @param {Object} opts
|
|
506
|
+
* @returns {Object}
|
|
507
|
+
*/
|
|
508
|
+
static #normalizeOptions(opts) {
|
|
509
|
+
const result = {};
|
|
510
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
511
|
+
const alias = ChameleonBackgrounds.#ALIASES[key];
|
|
512
|
+
result[alias ?? key] = value;
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Resolve a CSS selector string or HTMLElement to an HTMLElement.
|
|
519
|
+
* @param {string|HTMLElement} el
|
|
520
|
+
* @returns {HTMLElement}
|
|
521
|
+
*/
|
|
522
|
+
static #resolveElement(el) {
|
|
523
|
+
if (el instanceof HTMLElement) return el;
|
|
524
|
+
if (typeof el === 'string') {
|
|
525
|
+
if (el === 'body') return document.body;
|
|
526
|
+
const found = document.querySelector(el);
|
|
527
|
+
if (!found) throw new Error(`[ChameleonBackgrounds] Element not found: ${el}`);
|
|
528
|
+
return /** @type {HTMLElement} */ (found);
|
|
529
|
+
}
|
|
530
|
+
throw new TypeError(`[ChameleonBackgrounds] Invalid element: ${el}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Generate a short unique ID for scoping styles.
|
|
535
|
+
* @returns {string}
|
|
536
|
+
*/
|
|
537
|
+
static #generateUID() {
|
|
538
|
+
// Prefer crypto.randomUUID where available, otherwise fall back
|
|
539
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
540
|
+
return crypto.randomUUID().slice(0, 8);
|
|
541
|
+
}
|
|
542
|
+
// Fallback: random alphanumeric string
|
|
543
|
+
return Array.from({ length: 8 }, () =>
|
|
544
|
+
'abcdefghijklmnopqrstuvwxyz0123456789'.charAt(Math.floor(Math.random() * 36))
|
|
545
|
+
).join('');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ChameleonBackgrounds);
|
|
550
|
+
|
|
354
551
|
|
|
355
|
-
|
|
356
|
-
|
|
552
|
+
__webpack_exports__ = __webpack_exports__["default"];
|
|
553
|
+
/******/ return __webpack_exports__;
|
|
554
|
+
/******/ })()
|
|
555
|
+
;
|
|
556
|
+
});
|
|
557
|
+
//# 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,cAAc,yBAAyB;AACvC;;AAEA;AACA,aAAa,kBAAkB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;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;AACA;AACA;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.3\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 * @property {'high'|'low'|'auto'} [fetchPriority='auto'] - fetchPriority for the first loaded image (e.g., 'high' for LCP images).\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 fetchPriority: 'auto',\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 fetch_priority: 'fetchPriority',\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 if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {\n img.fetchPriority = this.#options.fetchPriority;\n }\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
|
|
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 o=0;o<i.length;){var n=i[o++],r=i[o++];e.o(t,n)?0===r&&o++:0===r?Object.defineProperty(t,n,{enumerable:!0,value:i[o++]}):Object.defineProperty(t,n,{enumerable:!0,get:r})}else for(var n in i)e.o(i,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:i[n]})},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,fetchPriority:"auto"});static#t=Object.freeze({transition_duration:"transitionDuration",slider_duration:"sliderDuration",slider_loop:"sliderLoop",min_overlay:"minOverlay",overlay_color:"overlayColor",overlay_image:"overlayImage",fetch_priority:"fetchPriority"});#i;#o;#n;#r;#s=null;#l=null;#d=!1;constructor(e={}){const t=i.#a(e);this.#i={...i.#e,...t},this.#n=i.#h(),this.#o=i.#c(this.#i.element),this.#r=this.#o.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.#o.querySelector(`#cbg-inner-${this.#n}`);if(e&&e.parentNode===this.#o){for(;e.firstChild;)this.#o.insertBefore(e.firstChild,e);this.#o.removeChild(e)}const t=this.#o.querySelector(`.cbg-loader-${this.#n}`);t&&t.remove(),this.#o.style.backgroundImage=""}get options(){return{...this.#i}}#u(){this.#y(),this.#m(),this.#o.matches("body")?queueMicrotask(()=>this.#p()):"complete"===document.readyState?this.#p():window.addEventListener("load",()=>this.#p(),{once:!0})}#y(){const e=this.#n,t="body"===this.#i.element||this.#o.matches("body")?"body":this.#i.element,i=this.#i.transitionDuration/1e3,o=`\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.#o.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 `,n=document.createElement("style");n.dataset.chameleonUid=e,n.textContent=o,document.head.appendChild(n),this.#s=n}#m(){const e=this.#n,t=document.createElement("div");for(t.id=`cbg-inner-${e}`;this.#o.firstChild;)t.appendChild(this.#o.firstChild);const i=document.createElement("div");i.classList.add(`cbg-loader-${e}`),this.#o.appendChild(t),this.#o.appendChild(i)}#p(){this.#d||("single"===this.#i.type?this.#f("string"==typeof this.#i.src?this.#i.src:this.#i.src[0]):"slider"===this.#i.type&&this.#g())}#f(e,t=!0){return this.#d?Promise.resolve():new Promise(i=>{const o=new Image;t&&"fetchPriority"in o&&"auto"!==this.#i.fetchPriority&&(o.fetchPriority=this.#i.fetchPriority),o.onload=()=>{if(this.#d)return i();this.#o.style.backgroundImage=`url(${e})`,this.#o.style.backgroundSize="cover";const t=this.#o.querySelector(`.cbg-loader-${this.#n}`);t&&(t.style.opacity=String(this.#i.minOverlay)),i()},o.onerror=()=>{console.warn(`[ChameleonBackgrounds] Failed to load image: ${e}`),i()},o.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.#o.querySelector(`.cbg-loader-${this.#n}`);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.#f(t,!1).then(e)}else"slider"===this.#i.type&&(this.#g(),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.#y()}#g(){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.#f(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.#o.querySelector(`.cbg-loader-${this.#n}`);t&&(t.style.opacity="1"),setTimeout(()=>{this.#d||this.#f(e,!1)},this.#i.transitionDuration)}static#a(e){const t={};for(const[o,n]of Object.entries(e))t[i.#t[o]??o]=n;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 o=i;return e.d(t,["default",0,o]),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.3",
|
|
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.3
|
|
18
18
|
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
19
19
|
* @license MIT
|
|
20
20
|
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* @property {number} [transitionDuration=2000] - Fade duration in milliseconds.
|
|
35
35
|
* @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.
|
|
36
36
|
* @property {boolean} [sliderLoop=false] - Whether the slider restarts after the last slide.
|
|
37
|
+
* @property {'high'|'low'|'auto'} [fetchPriority='auto'] - fetchPriority for the first loaded image (e.g., 'high' for LCP images).
|
|
37
38
|
*/
|
|
38
39
|
|
|
39
40
|
class ChameleonBackgrounds {
|
|
@@ -48,6 +49,7 @@ class ChameleonBackgrounds {
|
|
|
48
49
|
transitionDuration: 2000,
|
|
49
50
|
sliderDuration: 8000,
|
|
50
51
|
sliderLoop: false,
|
|
52
|
+
fetchPriority: 'auto',
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
// Legacy snake_case → camelCase alias map
|
|
@@ -58,6 +60,7 @@ class ChameleonBackgrounds {
|
|
|
58
60
|
min_overlay: 'minOverlay',
|
|
59
61
|
overlay_color: 'overlayColor',
|
|
60
62
|
overlay_image: 'overlayImage',
|
|
63
|
+
fetch_priority: 'fetchPriority',
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
/** @type {ChameleonOptions} */
|
|
@@ -268,6 +271,9 @@ class ChameleonBackgrounds {
|
|
|
268
271
|
|
|
269
272
|
return new Promise((resolve) => {
|
|
270
273
|
const img = new Image();
|
|
274
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
275
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
276
|
+
}
|
|
271
277
|
|
|
272
278
|
img.onload = () => {
|
|
273
279
|
if (this.#destroyed) return resolve();
|
|
@@ -324,7 +330,7 @@ class ChameleonBackgrounds {
|
|
|
324
330
|
return new Promise((resolve) => {
|
|
325
331
|
setTimeout(() => {
|
|
326
332
|
if (this.#destroyed) return resolve();
|
|
327
|
-
|
|
333
|
+
|
|
328
334
|
if (this.#options.type === 'single') {
|
|
329
335
|
const singleSrc = typeof this.#options.src === 'string' ? this.#options.src : this.#options.src[0];
|
|
330
336
|
this.#loadBackground(singleSrc, false).then(resolve);
|