PageSourceSearch

https://www.triton.de/typo3conf/ext/dstemplate/Resources/Public/Re…/js/adaptive-video-bg.js?1777115284

js triton.de collected 2026-10-08 05:46:47 UTC 8,549 bytes, 206 lines download raw bytes

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.