1/** 2 * Responsive Videos Handler. 3 * 4 * Handles switching video sources based on viewport size for responsive video backgrounds. 5 * Works with both container and slider blocks that have responsive video backgrounds. 6 * 7 * @since x.x.x 8 */ 9 10( function () { 11 'use strict'; 12 13 // Fallback only: Spectra's historical breakpoints, used when the bands the 14 // stylesheet was generated with are not on the page. 15 const FALLBACK_BREAKPOINTS = { 16 'base': 1024, // Desktop: 1024px and above. 17 '@tablet': 768, // Tablet: 768px to 1023.98px. 18 '@mobile': 0, // Mobile: 0px to 767.98px. 19 }; 20 21 /** 22 * Get the device whose band the viewport is in right now. 23 * 24 * The bands are the media queries the per-device CSS was generated with, 25 * published by the plugin as `window.spectraBlocksViewportBands` 26 * ({ desktop, tablet, mobile }). Matching against them keeps this switch in 27 * step with the stylesheet whatever the breakpoints are â core's defaults, 28 * the theme's, or Spectra's. The width comparison is only reached when that 29 * data is absent. 30 * 31 * @return {string} Device key: 'base', '@tablet', or '@mobile'. 32 */ 33 function getCurrentDevice() { 34 const bands = window.spectraBlocksViewportBands; 35 36 if ( bands && typeof window.matchMedia === 'function' ) { 37 const mobile = bands.mobile ? window.matchMedia( bands.mobile ) : null; 38 const tablet = bands.tablet ? window.matchMedia( bands.tablet ) : null; 39 // A query this browser cannot parse reports `not all`; fall back to widths. 40 const parsable = ( mq ) => mq && mq.media !== 'not all'; 41 if ( parsable( mobile ) && parsable( tablet ) ) { 42 if ( mobile.matches ) { 43 return '@mobile'; 44 } 45 if ( tablet.matches ) { 46 return '@tablet'; 47 } 48 return 'base'; 49 } 50 } 51 52 const width = window.innerWidth; 53 54 if ( width >= FALLBACK_BREAKPOINTS.base ) { 55 return 'base'; 56 } else if ( width >= FALLBACK_BREAKPOINTS[ '@tablet' ] ) { 57 return '@tablet'; 58 } 59 return '@mobile'; 60 } 61 62 /** 63 * Update video source based on current device and available responsive videos. 64 * 65 * @param {HTMLElement} container The container element with data-responsive-videos attribute. 66 */ 67 function updateVideoSource( container ) { 68 const video = container.querySelector( '.spectra-background-video__wrapper video' ); 69 70 if ( ! video ) { 71 return; 72 } 73 74 const responsiveVideosData = container.getAttribute( 'data-responsive-videos' ); 75 76 if ( ! responsiveVideosData ) { 77 return; 78 } 79 80 let responsiveVideos; 81 try { 82 responsiveVideos = JSON.parse( responsiveVideosData ); 83 } catch ( error ) { 84 return; 85 } 86 87 const currentDevice = getCurrentDevice(); 88 89 // Check if device actually changed. 90 const lastDevice = container.getAttribute( 'data-last-device' ); 91 if ( lastDevice === currentDevice ) { 92 return; 93 } 94 95 // Each viewport resolves over base only â core's inheritance model, 96 // the same one the CSS generator follows. Legacy tablet-only videos 97 // reach '@mobile' through the baked cascade before PHP emits the JSON. 98 const fallbackOrder = { 99 '@mobile': [ '@mobile', 'base' ], 100 '@tablet': [ '@tablet', 'base' ], 101 'base': [ 'base' ], 102 }; 103 104 // Find the appropriate video URL using fallback hierarchy. 105 let videoUrl = null; 106 const deviceOrder = fallbackOrder[ currentDevice ] || [ 'base' ]; 107 108 for ( const device of deviceOrder ) { 109 // An explicit empty entry means "this band has no video" (its 110 // background is an image or none) and must not fall back to base. 111 if ( Object.prototype.hasOwnProperty.call( responsiveVideos, device ) ) { 112 videoUrl = responsiveVideos[ device ] || null; 113 break; 114 } 115 } 116 117 const source = video.querySelector( 'source' ); 118 const currentSrc = source ? source.getAttribute( 'src' ) : video.getAttribute( 'src' ); 119 120 if ( ! videoUrl ) { 121 // This band has no video. The element is still in the DOM (hidden by 122 // the band's CSS), so stop it and drop its source: with a source 123 // attached the browser keeps the file buffered for a width that 124 // never shows it. 125 video.pause(); 126 if ( currentSrc ) { 127 if ( source ) { 128 source.removeAttribute( 'src' ); 129 } else { 130 video.removeAttribute( 'src' ); 131 } 132 video.load(); 133 } 134 } else { 135 if ( currentSrc !== videoUrl ) { 136 if ( source ) { 137 source.src = videoUrl; 138 } else { 139 video.src = videoUrl; 140 } 141 video.load(); 142 } 143 // PHP renders without `autoplay` (and with `preload="none"`) when some 144 // band has no video, so the browser does not fetch the file for a 145 // width that never shows it; this band does, so start it here. 146 if ( video.paused ) { 147 const playing = video.play(); 148 if ( playing && 'function' === typeof playing.catch ) { 149 playing.catch( function () {} ); 150 } 151 } 152 } 153 154 // Update the last device. 155 container.setAttribute( 'data-last-device', currentDevice ); 156 } 157 158 /** 159 * Initialize responsive videos for all elements on the page. 160 */ 161 function initResponsiveVideos() { 162 const containers = document.querySelectorAll( '[data-responsive-videos]' ); 163
164 containers.forEach( function ( container ) { 165 updateVideoSource( container ); 166 } ); 167 } 168 169 /** 170 * Handle window resize events with debouncing. 171 */ 172 let resizeTimeout; 173 function handleResize() { 174 clearTimeout( resizeTimeout ); 175 resizeTimeout = setTimeout( function () { 176 initResponsiveVideos(); 177 }, 250 ); // 250ms debounce. 178 } 179 180 /** 181 * Initialize when DOM is ready. 182 */ 183 function init() { 184 // Apply the viewport's own video straight away. PHP cannot know the 185 // viewport and always emits the base source; skipping this step (as an 186 // earlier version did, to avoid a flicker) left tablet and phone visitors 187 // on the desktop video until a resize crossed a band. The swap is a no-op 188 // at desktop widths, so nothing flickers there. 189 initResponsiveVideos(); 190 191 // Listen for window resize events. 192 window.addEventListener( 'resize', handleResize ); 193 194 // Also listen for orientation change on mobile devices. 195 window.addEventListener( 'orientationchange', function () { 196 setTimeout( initResponsiveVideos, 500 ); 197 } ); 198 } 199 200 // Initialize when DOM is ready. 201 if ( document.readyState === 'loading' ) { 202 document.addEventListener( 'DOMContentLoaded', init ); 203 } else { 204 init(); 205 } 206} )();
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.