1/** 2 * adaptive-video-bg.js 3 * TRITON Website Relaunch 2025 4 * 5 * Simplified background video loader that always serves one fixed video. 6 * 7 * Usage (ES Module): 8 * import { initAdaptiveVideoBg } from './adaptive-video-bg.js'; 9 * initAdaptiveVideoBg('#hero', { onVideoReady: (q) => console.log(q) }); 10 * 11 * Usage (global script tag): 12 * <script src="adaptive-video-bg.js"></script> 13 * <script>initAdaptiveVideoBg('#hero');</script> 14 */ 15 16// --------------------------------------------------------------------------- 17// Default asset paths (relative to HTML files in public/) 18// --------------------------------------------------------------------------- 19const DEFAULT_PATHS = { 20 video: 'https://cdn.triton.de/triton-de-hero-bg-video-1080p.mp4', 21 poster: 'media/images/backgrounds/triton.de-video-hero-bg.jpg', 22}; 23 24// --------------------------------------------------------------------------- 25// buildVideoElement 26// Creates a fully-configured <video> element for background use. 27// --------------------------------------------------------------------------- 28function buildVideoElement(src, posterSrc) { 29 const video = document.createElement('video'); 30 31 // Attributes required for autoplay in all browsers. 32 video.setAttribute('autoplay', ''); 33 video.setAttribute('muted', ''); 34 video.setAttribute('loop', ''); 35 video.setAttribute('playsinline', ''); 36 // Property must also be set â Chrome ignores the muted attribute 37 // on dynamically created video elements (known browser quirk). 38 video.muted = true; 39 40 // Poster is shown by the browser until the first frame is decoded. 41 video.setAttribute('poster', posterSrc); 42 43 video.src = src; 44 45 // Accessibility: decorative video, hidden from screen readers. 46 video.setAttribute('aria-hidden', 'true'); 47 48 // Inline styles to make the video fill its container like a CSS background. 49 Object.assign(video.style, { 50 position: 'absolute', 51 top: '0', 52 left: '0', 53 width: '100%', 54 height: '100%', 55 objectFit: 'cover', 56 opacity: '0', 57 transition: 'opacity 0.6s ease', 58 zIndex: '0', 59 }); 60 61 return video; 62} 63 64// --------------------------------------------------------------------------- 65// initAdaptiveVideoBg (main export) 66// --------------------------------------------------------------------------- 67/** 68 * Initialises a fixed background video inside the given container. 69 * 70 * @param {string} containerSelector â CSS selector for the target element. 71 * @param {object} [options] â Optional overrides. 72 * @param {string} [options.videoSrc] â Override fixed video path. 73 * @param {string} [options.hqSrc] â Legacy alias; still supported. 74 * @param {string} [options.posterSrc]â Override poster/fallback image path. 75 * @param {Function} [options.onVideoReady] â Callback(quality) when video plays. 76 */ 77async function initAdaptiveVideoBg(containerSelector, options = {}) { 78 // ------------------------------------------------------------------ 79 // 1. Resolve the container element. 80 // ------------------------------------------------------------------ 81 const container = document.querySelector(containerSelector); 82 if (!container) { 83 console.error(`[AdaptiveVideoBg] Container not found: "${containerSelector}"`); 84 return; 85 } 86 87 // Resolve asset paths (options override defaults). 88 const paths = { 89 video: options.videoSrc || options.hqSrc || DEFAULT_PATHS.video, 90 poster: options.posterSrc || DEFAULT_PATHS.poster, 91 }; 92 93 const onVideoReady = typeof options.onVideoReady === 'function' 94 ? options.onVideoReady 95 : null; 96 97 // ------------------------------------------------------------------ 98 // 2. Ensure the container is positioned so absolutely-placed children 99 // are clipped correctly. 100 // ------------------------------------------------------------------ 101 const computedPosition = getComputedStyle(container).position; 102 if (computedPosition === 'static') { 103 container.style.position = 'relative'; 104 } 105 106 // Mark the container with the module's CSS class. 107 container.classList.add('adaptive-video-bg-container'); 108 109 // ------------------------------------------------------------------ 110 // 3. Show the poster image immediately as a CSS background. 111 // This gives instant visual feedback while the video buffers. 112 // ------------------------------------------------------------------ 113 container.style.backgroundImage = `url("${paths.poster}")`; 114 container.style.backgroundSize = 'cover'; 115 container.style.backgroundPosition = 'center center'; 116 container.classList.add('adaptive-video-bg-poster'); 117 118 // ------------------------------------------------------------------ 119 // 4. Use one fixed video source. 120 // ------------------------------------------------------------------ 121 const quality = 'hq'; // kept for compatibility with existing callback logic 122 const videoSrc = paths.video; 123 124 console.log(`[AdaptiveVideoBg] Selected quality: ${quality.toUpperCase()}`); 125 console.log(`[AdaptiveVideoBg] Using fixed video source: ${videoSrc}`); 126 127 // ------------------------------------------------------------------ 128 // 5. Create and configure the <video> element. 129 // ------------------------------------------------------------------ 130 const video = buildVideoElement(videoSrc, paths.poster); 131 132 // ------------------------------------------------------------------ 133 // 6. Register event handlers before inserting into the DOM. 134 // ------------------------------------------------------------------ 135
136 // Safety fallback: if canplay never fires (e.g. codec issue, local file quirk), 137 // force the video visible after 3 seconds so the user sees something. 138 const showFallback = setTimeout(() => { 139 console.warn('[AdaptiveVideoBg] canplay did not fire â forcing video visible.'); 140 video.style.opacity = '1'; 141 container.style.backgroundImage = 'none'; 142 container.classList.remove('adaptive-video-bg-poster'); 143 container.classList.add('adaptive-video-bg-ready'); 144 }, 3000); 145 146 // canplay fires as soon as the browser can start playback. 147 video.addEventListener('canplay', () => { 148 clearTimeout(showFallback); 149 // Fade in the video. 150 video.style.opacity = '1'; 151 152 // Remove the static poster background from the container so the 153 // video is the only visible layer. 154 container.style.backgroundImage = 'none'; 155 container.classList.remove('adaptive-video-bg-poster'); 156 container.classList.add('adaptive-video-bg-ready'); 157 158 console.log(`[AdaptiveVideoBg] Video ready (${quality.toUpperCase()}): ${videoSrc}`); 159 160 if (onVideoReady) { 161 onVideoReady(quality); 162 } 163 }, { once: true }); 164 165 // Graceful error handling: if the video fails to load the poster 166 // stays visible, which is already the correct fallback state. 167 video.addEventListener('error', (e) => { 168 console.error( 169 `[AdaptiveVideoBg] Video failed to load (${quality.toUpperCase()}): ${videoSrc}`, 170 e 171 ); 172 // The poster background-image on the container remains, so the 173 // user still sees a meaningful visual. No further action needed. 174 }); 175 176 // ------------------------------------------------------------------ 177 // 7. Insert the video as the first child so it sits behind other 178 // content inside the container (z-index: 0). 179 // ------------------------------------------------------------------ 180 container.insertBefore(video, container.firstChild); 181 182 // Trigger load explicitly (some browsers defer loading on hidden elements). 183 video.load(); 184} 185 186// --------------------------------------------------------------------------- 187// Universal module export 188// Works both as a plain <script> (sets window.initAdaptiveVideoBg) and as an 189// ES module (exports the function for import statements). 190// --------------------------------------------------------------------------- 191(function (global) { 192 // ES Module environment â use named export. 193 if (typeof module !== 'undefined' && module.exports) { 194 // CommonJS (Node.js / bundlers) 195 module.exports = { initAdaptiveVideoBg }; 196 } else if (typeof exports !== 'undefined') { 197 exports.initAdaptiveVideoBg = initAdaptiveVideoBg; 198 } 199 200 // Always attach to window so <script> usage works regardless of module type. 201 if (typeof global !== 'undefined' && typeof global.window !== 'undefined') { 202 global.window.initAdaptiveVideoBg = initAdaptiveVideoBg; 203 } else if (typeof window !== 'undefined') { 204 window.initAdaptiveVideoBg = initAdaptiveVideoBg; 205 } 206})(typeof globalThis !== 'undefined' ? globalThis : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.