chameleon-backgrounds 2.1.2 → 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)`
|
|
@@ -83,7 +83,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
83
83
|
* |___/
|
|
84
84
|
*
|
|
85
85
|
* @module ChameleonBackgrounds
|
|
86
|
-
* @version 2.1.
|
|
86
|
+
* @version 2.1.3
|
|
87
87
|
* @author Lennart van Ballegoij (https://weblenn.com/)
|
|
88
88
|
* @license MIT
|
|
89
89
|
* @see https://github.com/WebLenn/ChameleonBackgrounds
|
|
@@ -103,6 +103,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
103
103
|
* @property {number} [transitionDuration=2000] - Fade duration in milliseconds.
|
|
104
104
|
* @property {number} [sliderDuration=8000] - Time each slide is shown in milliseconds.
|
|
105
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).
|
|
106
107
|
*/
|
|
107
108
|
|
|
108
109
|
class ChameleonBackgrounds {
|
|
@@ -117,6 +118,7 @@ class ChameleonBackgrounds {
|
|
|
117
118
|
transitionDuration: 2000,
|
|
118
119
|
sliderDuration: 8000,
|
|
119
120
|
sliderLoop: false,
|
|
121
|
+
fetchPriority: 'auto',
|
|
120
122
|
});
|
|
121
123
|
|
|
122
124
|
// Legacy snake_case → camelCase alias map
|
|
@@ -127,6 +129,7 @@ class ChameleonBackgrounds {
|
|
|
127
129
|
min_overlay: 'minOverlay',
|
|
128
130
|
overlay_color: 'overlayColor',
|
|
129
131
|
overlay_image: 'overlayImage',
|
|
132
|
+
fetch_priority: 'fetchPriority',
|
|
130
133
|
});
|
|
131
134
|
|
|
132
135
|
/** @type {ChameleonOptions} */
|
|
@@ -337,6 +340,9 @@ class ChameleonBackgrounds {
|
|
|
337
340
|
|
|
338
341
|
return new Promise((resolve) => {
|
|
339
342
|
const img = new Image();
|
|
343
|
+
if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {
|
|
344
|
+
img.fetchPriority = this.#options.fetchPriority;
|
|
345
|
+
}
|
|
340
346
|
|
|
341
347
|
img.onload = () => {
|
|
342
348
|
if (this.#destroyed) return resolve();
|
|
@@ -1 +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
|
+
{"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",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* |___/
|
|
15
15
|
*
|
|
16
16
|
* @module ChameleonBackgrounds
|
|
17
|
-
* @version 2.1.
|
|
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();
|