chameleon-backgrounds 2.1.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -11
- package/dist/chameleon-backgrounds.js +299 -114
- package/dist/chameleon-backgrounds.js.map +1 -1
- package/dist/chameleon-backgrounds.min.js +1 -1
- package/dist/chameleon-backgrounds.min.js.LICENSE.txt +1 -1
- package/package.json +1 -1
- package/src/chameleon-backgrounds.js +299 -114
|
@@ -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,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
|
+
{"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,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA,aAAa,QAAQ;AACrB,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6DAA6D;AAC3E,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;AACA;AACA;AACA,GAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH,aAAa,SAAS;AACtB;;AAEA,aAAa,kBAAkB;AAC/B;;AAEA,aAAa,aAAa;AAC1B;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,aAAa;AAC1B;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,SAAS;AACtB;;AAEA,aAAa,QAAQ;AACrB;;AAEA,aAAa,SAAS;AACtB;;AAEA,aAAa,2BAA2B;AACxC;;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;;AAEA;AACA,+CAA+C,UAAU;;AAEzD;AACA,8DAA8D,UAAU;AACxE;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,8DAA8D,UAAU;AACxE;AACA;AACA;;AAEA;AACA,+EAA+E,UAAU;AACzF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,eAAe;AACf;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT,OAAO;AACP;AACA,MAAM;AACN;AACA;AACA,QAAQ;AACR;AACA;AACA,UAAU;AACV,8EAA8E,YAAY;AAC1F;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;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;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,4CAA4C,UAAU;AACtD,yDAAyD,wCAAwC;AACjG;;AAEA;AACA,oEAAoE,2BAA2B;AAC/F,MAAM;AACN;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,8BAA8B,IAAI;AAClC;;AAEA;AACA;AACA;AACA;;AAEA;AACA,qDAAqD,IAAI;;AAEzD;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA,8DAA8D,oBAAoB;AAClF,aAAa,6BAA6B;AAC1C,aAAa,UAAU;AACvB,aAAa,UAAU;AACvB,eAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,0EAA0E,UAAU;AACpF,uDAAuD,SAAS;;AAEhE;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA,0DAA0D,SAAS;AACnE;AACA;AACA;AACA;AACA,WAAW;;AAEX,UAAU;AACV;AACA,wDAAwD,SAAS;AACjE;AACA;;AAEA,oEAAoE,UAAU;AAC9E;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA;AACA,mBAAmB;AACnB;;AAEA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA,aAAa,QAAQ;AACrB,eAAe;AACf;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;;AAEA;AACA,sBAAsB;;AAEtB;AACA,2DAA2D;AAC3D;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA,YAAY;AACZ;AACA;AACA;AACA;;AAEA;AACA,OAAO;AACP,KAAK;AACL;;AAEA;AACA;AACA,aAAa,6BAA6B;AAC1C,aAAa,QAAQ;AACrB,eAAe;AACf;AACA;AACA;;AAEA;AACA;AACA,kEAAkE,UAAU;AAC5E;AACA;AACA;;AAEA;AACA;AACA;AACA,SAAS;AACT,QAAQ;AACR;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 3.0.1\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} ChameleonImageConfig\n * @property {string} url - The fallback URL.\n * @property {string} [srcset] - Optional srcset.\n * @property {string} [sizes] - Optional sizes.\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[]|ChameleonImageConfig|ChameleonImageConfig[]} src - Image URL(s) or config object(s).\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 * @property {boolean} [lazyLoad=true] - Whether to wait until element is in viewport using IntersectionObserver.\n * @property {'solid'|'crossfade'} [transitionMode='solid'] - Transition mode: 'solid' (fade to color) or 'crossfade' (fade between 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 lazyLoad: true,\n transitionMode: 'solid',\n respectReducedMotion: 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 fetch_priority: 'fetchPriority',\n lazy_load: 'lazyLoad',\n transition_mode: 'transitionMode',\n respect_reduced_motion: 'respectReducedMotion',\n });\n\n /** @type {boolean} */\n static #globalStylesInjected = false;\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 {number|null} */\n #sliderIntervalId = null;\n\n /** @type {number} */\n #currentSlideIndex = 0;\n\n /** @type {boolean} */\n #hasLoadedFirstBackground = false;\n\n /** @type {number} */\n #transitionId = 0;\n\n /** @type {boolean} */\n #isPaused = false;\n\n /** @type {IntersectionObserver|null} */\n #observer = 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 clearTimeout(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n\n if (this.#observer) {\n this.#observer.disconnect();\n this.#observer = null;\n }\n\n // Clean up dynamic classes on host\n this.#element.classList.remove(`cbg-host-${this.#uid}`, 'cbg-host');\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 temporary crossfade elements\n const crossfadeElements = this.#element.querySelectorAll(`.cbg-crossfade-${this.#uid}`);\n crossfadeElements.forEach(el => el.remove());\n\n // Remove inline CSS Variables and background-image\n this.#element.style.backgroundImage = '';\n this.#element.style.removeProperty('--cbg-duration');\n this.#element.style.removeProperty('--cbg-overlay-color');\n this.#element.style.removeProperty('--cbg-overlay-image');\n this.#element.style.removeProperty('--cbg-min-overlay');\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.#injectGlobalStyles();\n this.#updateCSSVariables();\n this.#buildDOM();\n\n // Check for prefers-reduced-motion to auto-pause slider if enabled\n if (this.#options.respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n this.#isPaused = true;\n }\n\n if (this.#options.lazyLoad && 'IntersectionObserver' in window) {\n this.#observer = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n this.#observer.disconnect();\n this.#observer = null;\n this.#retrieveBackground();\n }\n });\n });\n this.#observer.observe(this.#element);\n } else {\n if (this.#element.matches('body')) {\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 // ---------------------------------------------------------------------------\n // Play / Pause API\n // ---------------------------------------------------------------------------\n\n /**\n * Pause the slideshow.\n */\n pause() {\n if (this.#destroyed || this.#options.type !== 'slider') return;\n this.#isPaused = true;\n if (this.#sliderIntervalId !== null) {\n clearTimeout(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n }\n\n /**\n * Resume the slideshow.\n */\n play() {\n if (this.#destroyed || this.#options.type !== 'slider') return;\n this.#isPaused = false;\n // Only restart if not currently running\n if (this.#sliderIntervalId === null) {\n this.#startSliderLoop();\n }\n }\n\n // ---------------------------------------------------------------------------\n // DOM Construction\n // ---------------------------------------------------------------------------\n\n /**\n * Inject global styles once.\n */\n #injectGlobalStyles() {\n if (ChameleonBackgrounds.#globalStylesInjected) return;\n ChameleonBackgrounds.#globalStylesInjected = true;\n\n const css = `\n .cbg-host {\n position: relative;\n }\n body.cbg-host {\n /* body naturally acts as relative for background sizing if not explicitly positioned differently */\n }\n .cbg-inner {\n z-index: 2;\n position: relative;\n }\n .cbg-loader {\n height: 100%;\n width: 100%;\n position: absolute;\n top: 0;\n left: 0;\n background-image: var(--cbg-overlay-image, none);\n background-color: var(--cbg-overlay-color, transparent);\n opacity: 1;\n z-index: 1;\n transition: opacity var(--cbg-duration, 2s) ease;\n pointer-events: none;\n }\n body.cbg-host > .cbg-loader {\n position: fixed;\n }\n .cbg-crossfade-el {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n z-index: 0;\n opacity: 0;\n transition: opacity var(--cbg-duration, 2s) ease;\n pointer-events: none;\n }\n body.cbg-host > .cbg-crossfade-el {\n position: fixed;\n }\n `;\n\n const style = document.createElement('style');\n style.id = 'chameleon-global-styles';\n style.textContent = css;\n document.head.appendChild(style);\n }\n\n /**\n * Apply CSS custom properties to the host element.\n */\n #updateCSSVariables() {\n this.#element.classList.add(`cbg-host-${this.#uid}`, 'cbg-host');\n this.#element.style.setProperty('--cbg-duration', `${this.#options.transitionDuration / 1000}s`);\n this.#element.style.setProperty('--cbg-overlay-color', this.#options.overlayColor);\n\n if (this.#options.overlayImage) {\n this.#element.style.setProperty('--cbg-overlay-image', `url(${this.#options.overlayImage})`);\n } else {\n this.#element.style.setProperty('--cbg-overlay-image', 'none');\n }\n\n this.#element.style.setProperty('--cbg-min-overlay', String(this.#options.minOverlay));\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 wrapper.classList.add('cbg-inner');\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', `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 const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;\n this.#loadBackground(singleSrc);\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 * Handles both string URLs and responsive image objects ({ url, srcset, sizes }).\n * @param {string|ChameleonImageConfig} srcConfig - Image configuration or URL.\n * @param {boolean} [isFirst=true] - Whether this is the first image (skips overlay reset).\n * @param {boolean} [isCrossfade=false] - If true, handles true crossfading without solid color drop.\n * @returns {Promise<void>}\n */\n #loadBackground(srcConfig, isFirst = true, isCrossfade = false) {\n if (this.#destroyed) return Promise.resolve();\n\n return new Promise((resolve) => {\n const img = new Image();\n\n let urlStr = srcConfig;\n if (typeof srcConfig === 'object' && srcConfig.url) {\n urlStr = srcConfig.url;\n if (srcConfig.srcset) img.srcset = srcConfig.srcset;\n if (srcConfig.sizes) img.sizes = srcConfig.sizes;\n }\n\n if (isFirst && 'fetchPriority' in img && this.#options.fetchPriority !== 'auto') {\n img.fetchPriority = this.#options.fetchPriority;\n }\n\n // Append to DOM to ensure viewport-based `sizes` calculate correctly in all browsers\n img.style.position = 'absolute';\n img.style.visibility = 'hidden';\n img.style.width = '0';\n img.style.height = '0';\n this.#element.appendChild(img);\n\n img.onload = () => {\n img.remove();\n if (this.#destroyed) return resolve();\n\n // `img.currentSrc` retrieves the actual srcset chosen file, otherwise fallback to `urlStr`\n const finalUrl = img.currentSrc || urlStr;\n\n if (isCrossfade && !isFirst) {\n // --- Crossfade Transition Mode ---\n const crossfadeDiv = document.createElement('div');\n crossfadeDiv.classList.add('cbg-crossfade-el', `cbg-crossfade-${this.#uid}`);\n crossfadeDiv.style.backgroundImage = `url(\"${finalUrl}\")`;\n\n // Insert right behind the loader (or directly at start of host)\n this.#element.insertBefore(crossfadeDiv, this.#element.firstChild);\n\n // Trigger layout so the transition works\n void crossfadeDiv.offsetWidth;\n\n crossfadeDiv.style.opacity = '1';\n\n setTimeout(() => {\n if (this.#destroyed) return resolve();\n this.#element.style.backgroundImage = `url(\"${finalUrl}\")`;\n this.#element.style.backgroundSize = 'cover';\n this.#element.style.backgroundRepeat = 'no-repeat';\n crossfadeDiv.remove();\n resolve();\n }, this.#options.transitionDuration);\n\n } else {\n // --- Solid Transition Mode or First Load ---\n this.#element.style.backgroundImage = `url(\"${finalUrl}\")`;\n this.#element.style.backgroundSize = 'cover';\n this.#element.style.backgroundRepeat = 'no-repeat';\n\n const loader = this.#element.querySelector(`.cbg-loader-${this.#uid}`);\n if (loader) {\n loader.style.opacity = String(this.#options.minOverlay);\n }\n this.#hasLoadedFirstBackground = true;\n setTimeout(() => {\n resolve();\n }, this.#options.transitionDuration);\n }\n };\n\n img.onerror = () => {\n img.remove();\n console.warn(`[ChameleonBackgrounds] Failed to load image:`, srcConfig);\n resolve(); // resolve anyway to keep logic flowing\n };\n\n img.src = urlStr;\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 this.#transitionId++;\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.\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 clearTimeout(this.#sliderIntervalId);\n this.#sliderIntervalId = null;\n }\n\n if (this.#options.type === 'single') {\n const singleSrc = Array.isArray(this.#options.src) ? this.#options.src[0] : this.#options.src;\n return this.#cycleSliderSlide(singleSrc, this.#transitionId);\n } else if (this.#options.type === 'slider') {\n this.#startSlider(this.#transitionId);\n return Promise.resolve();\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 this.#updateCSSVariables();\n // In v1/v2, reloadOptions did not restart the slider; the user called reloadBackground.\n // We intentionally do not auto-restart here to prevent concurrent race conditions.\n }\n\n // ---------------------------------------------------------------------------\n // Slider\n // ---------------------------------------------------------------------------\n\n /**\n * Start the background slideshow.\n * @param {number} tid - The transition ID\n */\n #startSlider(tid = this.#transitionId) {\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 this.#currentSlideIndex = 0;\n\n if (this.#hasLoadedFirstBackground) {\n // Instance already has a background, crossfade smoothly\n this.#cycleSliderSlide(sources[this.#currentSlideIndex], tid).then(() => {\n if (this.#destroyed || this.#transitionId !== tid) return;\n this.#currentSlideIndex = 1;\n if (sources.length > 1) this.#startSliderLoop();\n });\n } else {\n // First load, load immediately without solid color flash\n this.#loadBackground(sources[this.#currentSlideIndex], true, this.#options.transitionMode === 'crossfade').then(() => {\n if (this.#destroyed || this.#transitionId !== tid) return;\n this.#hasLoadedFirstBackground = true;\n this.#currentSlideIndex = 1;\n if (sources.length > 1) this.#startSliderLoop();\n });\n }\n }\n\n #startSliderLoop() {\n if (this.#destroyed || this.#isPaused) return;\n\n // Parse as integers to prevent string concatenation bugs if users passed strings\n const sDuration = parseInt(this.#options.sliderDuration, 10) || 8000;\n\n this.#sliderIntervalId = setTimeout(() => {\n if (this.#destroyed || this.#isPaused) {\n this.#sliderIntervalId = null;\n return;\n }\n\n const tid = this.#transitionId;\n this.#cycleSliderSlide(this.#options.src[this.#currentSlideIndex], tid).then(() => {\n if (this.#destroyed || this.#isPaused || this.#transitionId !== tid) {\n this.#sliderIntervalId = null;\n return;\n }\n\n this.#currentSlideIndex++;\n\n if (this.#currentSlideIndex >= this.#options.src.length) {\n if (this.#options.sliderLoop) {\n this.#currentSlideIndex = 0;\n } else {\n this.#sliderIntervalId = null;\n return;\n }\n }\n\n this.#startSliderLoop();\n });\n }, sDuration);\n }\n\n /**\n * Cycle to a specific slide without resetting the slider instance.\n * @param {string|ChameleonImageConfig} src - The image URL/config to load.\n * @param {number} tid - The transition ID\n * @returns {Promise<void>}\n */\n #cycleSliderSlide(src, tid = this.#transitionId) {\n if (this.#destroyed) return Promise.resolve();\n\n return new Promise(resolve => {\n if (this.#options.transitionMode === 'solid') {\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 || this.#transitionId !== tid) return resolve();\n this.#loadBackground(src, false, false).then(resolve);\n }, this.#options.transitionDuration);\n } else {\n // Crossfade mode triggers immediately\n this.#loadBackground(src, false, true).then(resolve);\n }\n });\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 s=0;s<i.length;){var r=i[s++],o=i[s++];e.o(t,r)?0===o&&s++:0===o?Object.defineProperty(t,r,{enumerable:!0,value:i[s++]}):Object.defineProperty(t,r,{enumerable:!0,get:o})}else for(var r in i)e.o(i,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:i[r]})},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",lazyLoad:!0,transitionMode:"solid",respectReducedMotion:!1});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",lazy_load:"lazyLoad",transition_mode:"transitionMode",respect_reduced_motion:"respectReducedMotion"});static#i=!1;#s;#r;#o;#n;#l=null;#d=0;#a=!1;#c=0;#h=!1;#u=null;#y=!1;constructor(e={}){const t=i.#p(e);this.#s={...i.#e,...t},this.#o=i.#m(),this.#r=i.#g(this.#s.element),this.#n=this.#r.innerHTML,this.#b()}destroy(){if(this.#y)return;this.#y=!0,null!==this.#l&&(clearTimeout(this.#l),this.#l=null),this.#u&&(this.#u.disconnect(),this.#u=null),this.#r.classList.remove(`cbg-host-${this.#o}`,"cbg-host");const e=this.#r.querySelector(`#cbg-inner-${this.#o}`);if(e&&e.parentNode===this.#r){for(;e.firstChild;)this.#r.insertBefore(e.firstChild,e);this.#r.removeChild(e)}const t=this.#r.querySelector(`.cbg-loader-${this.#o}`);t&&t.remove(),this.#r.querySelectorAll(`.cbg-crossfade-${this.#o}`).forEach(e=>e.remove()),this.#r.style.backgroundImage="",this.#r.style.removeProperty("--cbg-duration"),this.#r.style.removeProperty("--cbg-overlay-color"),this.#r.style.removeProperty("--cbg-overlay-image"),this.#r.style.removeProperty("--cbg-min-overlay")}get options(){return{...this.#s}}#b(){this.#v(),this.#f(),this.#I(),this.#s.respectReducedMotion&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches&&(this.#h=!0),this.#s.lazyLoad&&"IntersectionObserver"in window?(this.#u=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&(this.#u.disconnect(),this.#u=null,this.#S())})}),this.#u.observe(this.#r)):this.#r.matches("body")?queueMicrotask(()=>this.#S()):"complete"===document.readyState?this.#S():window.addEventListener("load",()=>this.#S(),{once:!0})}pause(){this.#y||"slider"!==this.#s.type||(this.#h=!0,null!==this.#l&&(clearTimeout(this.#l),this.#l=null))}play(){this.#y||"slider"!==this.#s.type||(this.#h=!1,null===this.#l&&this.#k())}#v(){if(i.#i)return;i.#i=!0;const e=document.createElement("style");e.id="chameleon-global-styles",e.textContent="\n .cbg-host {\n position: relative;\n }\n body.cbg-host {\n /* body naturally acts as relative for background sizing if not explicitly positioned differently */\n }\n .cbg-inner {\n z-index: 2;\n position: relative;\n }\n .cbg-loader {\n height: 100%;\n width: 100%;\n position: absolute;\n top: 0;\n left: 0;\n background-image: var(--cbg-overlay-image, none);\n background-color: var(--cbg-overlay-color, transparent);\n opacity: 1;\n z-index: 1;\n transition: opacity var(--cbg-duration, 2s) ease;\n pointer-events: none;\n }\n body.cbg-host > .cbg-loader {\n position: fixed;\n }\n .cbg-crossfade-el {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n z-index: 0;\n opacity: 0;\n transition: opacity var(--cbg-duration, 2s) ease;\n pointer-events: none;\n }\n body.cbg-host > .cbg-crossfade-el {\n position: fixed;\n }\n ",document.head.appendChild(e)}#f(){this.#r.classList.add(`cbg-host-${this.#o}`,"cbg-host"),this.#r.style.setProperty("--cbg-duration",this.#s.transitionDuration/1e3+"s"),this.#r.style.setProperty("--cbg-overlay-color",this.#s.overlayColor),this.#s.overlayImage?this.#r.style.setProperty("--cbg-overlay-image",`url(${this.#s.overlayImage})`):this.#r.style.setProperty("--cbg-overlay-image","none"),this.#r.style.setProperty("--cbg-min-overlay",String(this.#s.minOverlay))}#I(){const e=this.#o,t=document.createElement("div");for(t.id=`cbg-inner-${e}`,t.classList.add("cbg-inner");this.#r.firstChild;)t.appendChild(this.#r.firstChild);const i=document.createElement("div");i.classList.add("cbg-loader",`cbg-loader-${e}`),this.#r.appendChild(t),this.#r.appendChild(i)}#S(){if(!this.#y)if("single"===this.#s.type){const e=Array.isArray(this.#s.src)?this.#s.src[0]:this.#s.src;this.#P(e)}else"slider"===this.#s.type&&this.#L()}#P(e,t=!0,i=!1){return this.#y?Promise.resolve():new Promise(s=>{const r=new Image;let o=e;"object"==typeof e&&e.url&&(o=e.url,e.srcset&&(r.srcset=e.srcset),e.sizes&&(r.sizes=e.sizes)),t&&"fetchPriority"in r&&"auto"!==this.#s.fetchPriority&&(r.fetchPriority=this.#s.fetchPriority),r.style.position="absolute",r.style.visibility="hidden",r.style.width="0",r.style.height="0",this.#r.appendChild(r),r.onload=()=>{if(r.remove(),this.#y)return s();const e=r.currentSrc||o;if(i&&!t){const t=document.createElement("div");t.classList.add("cbg-crossfade-el",`cbg-crossfade-${this.#o}`),t.style.backgroundImage=`url("${e}")`,this.#r.insertBefore(t,this.#r.firstChild),t.offsetWidth,t.style.opacity="1",setTimeout(()=>{if(this.#y)return s();this.#r.style.backgroundImage=`url("${e}")`,this.#r.style.backgroundSize="cover",this.#r.style.backgroundRepeat="no-repeat",t.remove(),s()},this.#s.transitionDuration)}else{this.#r.style.backgroundImage=`url("${e}")`,this.#r.style.backgroundSize="cover",this.#r.style.backgroundRepeat="no-repeat";const t=this.#r.querySelector(`.cbg-loader-${this.#o}`);t&&(t.style.opacity=String(this.#s.minOverlay)),this.#a=!0,setTimeout(()=>{s()},this.#s.transitionDuration)}},r.onerror=()=>{r.remove(),console.warn("[ChameleonBackgrounds] Failed to load image:",e),s()},r.src=o})}reloadBackground(e){if(this.#y)return Promise.resolve();if(this.#c++,void 0!==e&&("slider"===this.#s.type&&"string"==typeof e?this.#s.src=e.includes(",")?e.split(",").map(e=>e.trim()):[e]:this.#s.src=e),null!==this.#l&&(clearTimeout(this.#l),this.#l=null),"single"===this.#s.type){const e=Array.isArray(this.#s.src)?this.#s.src[0]:this.#s.src;return this.#C(e,this.#c)}return"slider"===this.#s.type?(this.#L(this.#c),Promise.resolve()):void 0}reloadOptions(e){if(this.#y)return;const t=i.#p(e);this.#s={...this.#s,...t},this.#f()}#L(e=this.#c){if(this.#y)return;const t=this.#s.src;Array.isArray(t)&&0!==t.length?(this.#d=0,this.#a?this.#C(t[this.#d],e).then(()=>{this.#y||this.#c!==e||(this.#d=1,t.length>1&&this.#k())}):this.#P(t[this.#d],!0,"crossfade"===this.#s.transitionMode).then(()=>{this.#y||this.#c!==e||(this.#a=!0,this.#d=1,t.length>1&&this.#k())})):console.warn("[ChameleonBackgrounds] Slider mode requires an array of image URLs in `src`.")}#k(){if(this.#y||this.#h)return;const e=parseInt(this.#s.sliderDuration,10)||8e3;this.#l=setTimeout(()=>{if(this.#y||this.#h)return void(this.#l=null);const e=this.#c;this.#C(this.#s.src[this.#d],e).then(()=>{if(this.#y||this.#h||this.#c!==e)this.#l=null;else{if(this.#d++,this.#d>=this.#s.src.length){if(!this.#s.sliderLoop)return void(this.#l=null);this.#d=0}this.#k()}})},e)}#C(e,t=this.#c){return this.#y?Promise.resolve():new Promise(i=>{if("solid"===this.#s.transitionMode){const s=this.#r.querySelector(`.cbg-loader-${this.#o}`);s&&(s.style.opacity="1"),setTimeout(()=>{if(this.#y||this.#c!==t)return i();this.#P(e,!1,!1).then(i)},this.#s.transitionDuration)}else this.#P(e,!1,!0).then(i)})}static#p(e){const t={};for(const[s,r]of Object.entries(e))t[i.#t[s]??s]=r;return t}static#g(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#m(){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 s=i;return e.d(t,["default",0,s]),t=t.default,t})());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chameleon-backgrounds",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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",
|