(() => { const DEFAULT_BASE_URL = (() => { try { return new URL(document.currentScript?.src || window.location.href).origin; } catch { return window.location.origin; } })(); const STYLE = ` :host { display: block; width: var(--rbw-width, 100%); height: var(--rbw-height, 260px); } .frame { position: relative; width: 100%; height: 100%; overflow: hidden; border-radius: var(--rbw-radius, 14px); background: #eef2f7; border: 1px solid rgba(15, 23, 42, 0.08); box-shadow: 0 10px 28px rgba(15, 23, 42, 0.06); } img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: var(--rbw-fit, cover); filter: blur(var(--rbw-blur, 0px)); transform: scale(1.04); opacity: 0; transition: opacity 180ms ease; will-change: filter, opacity, transform; } .state { position: absolute; inset: 0; display: grid; place-items: center; padding: 16px; text-align: center; color: #5b657a; font: 13px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(180deg, rgba(255,255,255,.45), rgba(255,255,255,.2)); backdrop-filter: blur(4px); } :host([loaded]) .state, :host([error]) .state { opacity: 0; pointer-events: none; } :host([loaded]) img { opacity: 1; } `; const DEFAULTS = { mode: 'auto', blur: '0', radius: '14px', fit: 'cover', height: '260px', width: '100%', }; class RandomBackgroundWidget extends HTMLElement { static observedAttributes = ['src', 'mode', 'blur', 'radius', 'fit', 'height', 'width']; constructor() { super(); this.attachShadow({ mode: 'open' }); this._manifest = null; this._requestSeq = 0; this._boundRefresh = this.refresh.bind(this); } connectedCallback() { this.render(); this.refresh(); window.addEventListener('resize', this._boundRefresh, { passive: true }); } disconnectedCallback() { window.removeEventListener('resize', this._boundRefresh); } attributeChangedCallback() { if (this.isConnected) { this.render(); this.refresh(); } } get baseUrl() { return (this.getAttribute('src') || DEFAULT_BASE_URL).replace(/\/+$/, ''); } get mode() { return (this.getAttribute('mode') || DEFAULTS.mode).toLowerCase(); } get blur() { const raw = Number.parseFloat(this.getAttribute('blur') || DEFAULTS.blur); return Number.isFinite(raw) && raw > 0 ? raw : 0; } get radius() { return this.getAttribute('radius') || DEFAULTS.radius; } get fit() { return this.getAttribute('fit') || DEFAULTS.fit; } get height() { return this.getAttribute('height') || DEFAULTS.height; } get width() { return this.getAttribute('width') || DEFAULTS.width; } render() { if (!this.shadowRoot) return; this.style.setProperty('--rbw-width', this.width); this.style.setProperty('--rbw-height', this.height); this.style.setProperty('--rbw-radius', this.radius); this.style.setProperty('--rbw-fit', this.fit); this.style.setProperty('--rbw-blur', `${this.blur}px`); this.shadowRoot.innerHTML = `
random background
加载中
`; } async refresh() { const seq = ++this._requestSeq; const img = this.shadowRoot.querySelector('img'); const state = this.shadowRoot.querySelector('.state'); try { if (!this._manifest) { this._manifest = await this.fetchManifest(); } if (seq !== this._requestSeq) return; const variant = this.resolveVariant(); const data = this._manifest?.variants?.[variant] || {}; const folder = data.folder || (variant === 'mobile' ? 'mobile-image' : 'desketop-image'); const images = Array.isArray(data.images) ? data.images.filter((item) => typeof item === 'string') : []; if (!images.length) { throw new Error(`no images in ${folder}`); } const filename = images[Math.floor(Math.random() * images.length)]; img.src = `${this.baseUrl}/${folder}/${filename}`; img.alt = `random background ${variant}`; this.setAttribute('loaded', ''); this.removeAttribute('error'); state.textContent = ''; } catch (error) { this.removeAttribute('loaded'); this.setAttribute('error', ''); state.textContent = error instanceof Error ? error.message : 'load failed'; } } resolveVariant() { if (this.mode === 'mobile' || this.mode === 'desktop') return this.mode; const orientation = window.matchMedia('(orientation: portrait)').matches ? 'portrait' : 'landscape'; const device = this.detectDevice(); if (orientation === 'portrait') return 'mobile'; if (orientation === 'landscape') return 'desktop'; return device; } detectDevice() { if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') { return navigator.userAgentData.mobile ? 'mobile' : 'desktop'; } return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ? 'mobile' : 'desktop'; } async fetchManifest() { const url = new URL('manifest.json', `${this.baseUrl}/`); const response = await fetch(url.toString(), { mode: 'cors', cache: 'no-store' }); if (!response.ok) { throw new Error(`manifest ${response.status}`); } return await response.json(); } } if (!customElements.get('random-background-widget')) { customElements.define('random-background-widget', RandomBackgroundWidget); } window.RandomBackgroundWidget = RandomBackgroundWidget; })();