https://combsbrothersllc.com/wp-content/themes/enfold/config-templ…/slideshow/phone-track.js?ver=8.2
1/** 2 * Phone track - on phones the item sliders become one scrolling row. 3 * 4 * The sliders group their items into slides on the server: a slide is a wrapper holding as many 5 * items as the element has columns. That grouping is fixed in the html, so on a narrow screen css 6 * can only wrap those items onto more rows - which is why five products end up as 2 + 2 + 1 rows 7 * inside a single slide, and a swipe jumps all five at once. 8 * 9 * Below the phone breakpoint we take the element over instead: the wrappers are removed from the 10 * layout with display:contents (css), so every item becomes a sibling in one row that the browser 11 * scrolls and snaps natively, and the arrows scroll it by one screenful. 12 * 13 * A slider that is already running when the viewport narrows - a window being resized, a phone 14 * being turned - is frozen rather than taken apart: it has no working way to be stopped, and it 15 * does not need one, because a frozen slider writes nothing. Widening again thaws it and hands 16 * the element back. An element claimed before any slider started keeps its track at every width, 17 * laid out from its own column count, since starting a slider there would mean waiting out the 18 * image preloader with the element already emptied. 19 * 20 * @since 8.2 21 */ 22( function( $ ) 23{ 24 'use strict'; 25 26 var QUERY = '(max-width: 767px)', 27 SLIDERS = '.avia-content-slider-active', 28 CLAIMED = 'av-phone-track'; 29 30 function is_phone() 31 { 32 return 'function' === typeof window.matchMedia && window.matchMedia( QUERY ).matches; 33 } 34 35 function reduced_motion() 36 { 37 return 'function' === typeof window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; 38 } 39 40 function is_rtl( track ) 41 { 42 return 'rtl' === window.getComputedStyle( track ).direction; 43 } 44 45 function track_items( track ) 46 { 47 return track.querySelectorAll( '.slide-entry, .products .product' ); 48 } 49 50 /** 51 * Is the item completely inside the visible part of the track? 52 * 53 * Asking the items is the only reliable measure here: the scrollable width holds trailing space 54 * an item never reaches, so comparing against it keeps the next arrow lit at the end of the row, 55 * and it says nothing about which direction the page is written in. 56 */ 57 function fully_visible( track, item ) 58 { 59 if( ! item ) 60 { 61 return true; 62 } 63 64 var visible = track.getBoundingClientRect(), 65 rect = item.getBoundingClientRect(); 66 67 return rect.left >= visible.left - 1 && rect.right <= visible.right + 1; 68 } 69 70 /** 71 * The arrows are hidden by css until these classes say otherwise - the slider adds them when it 72 * starts up, so a claimed element has to do it itself. 73 */ 74 function update_arrows( $container, track ) 75 { 76 var items = track_items( track ), 77 $arrows = $container.find( '.avia-slideshow-arrows' ); 78 79 $arrows.toggleClass( 'av-visible-prev', items.length > 0 && ! fully_visible( track, items[0] ) ); 80 $arrows.toggleClass( 'av-visible-next', items.length > 0 && ! fully_visible( track, items[ items.length - 1 ] ) ); 81 } 82 83 /** 84 * Scrolls the track itself instead of asking for behavior:'smooth'. 85 * 86 * A smooth scroll requested from script is ignored outright in some browsers once the element 87 * snaps, and is not supported at all in older ones - both of which leave the arrows dead. An 88 * own animation behaves the same everywhere. Snapping is suspended while it runs, or it fights 89 * the animation for every frame; the position it ends on is a snap point anyway. 90 */ 91 function animate_scroll( track, target ) 92 { 93 var start = track.scrollLeft, 94 distance = target - start, 95 duration = 350, 96 began = null; 97 98 if( reduced_motion() || ! window.requestAnimationFrame ) 99 { 100 track.scrollLeft = target; 101 return; 102 } 103 104 track.style.scrollSnapType = 'none'; 105 106 window.requestAnimationFrame( function step( now ) 107 { 108 began = began || now; 109 110 var progress = Math.min( 1, ( now - began ) / duration ); 111 112 // ease out, so it settles rather than stops 113 track.scrollLeft = start + distance * ( 1 - Math.pow( 1 - progress, 3 ) ); 114 115 if( progress < 1 ) 116 { 117 window.requestAnimationFrame( step ); 118 } 119 else 120 {
121 track.style.scrollSnapType = ''; 122 } 123 } ); 124 } 125 126 /** 127 * Where the track would have to stand for this item to sit at its leading edge. 128 */ 129 function position_of( track, item ) 130 { 131 return track.scrollLeft + ( item.getBoundingClientRect().left - track.getBoundingClientRect().left ); 132 } 133 134 function scroll_page( track, direction ) 135 { 136 var items = track_items( track ), 137 step = track.clientWidth * direction * ( is_rtl( track ) ? -1 : 1 ), 138 target = track.scrollLeft + step, 139 positions = []; 140 141 // stop at the first and the last item rather than at the edge of the scrollable area - the 142 // area is wider than the items reach, and scrolling into that space only snaps back. 143 // taking both ends from the items keeps this right when the page runs right to left, where 144 // the scroll position counts down from zero 145 Array.prototype.forEach.call( items, function( item ) 146 { 147 positions.push( position_of( track, item ) ); 148 } ); 149 150 if( positions.length ) 151 { 152 target = Math.max( Math.min.apply( null, positions ), Math.min( Math.max.apply( null, positions ), target ) ); 153 } 154 155 animate_scroll( track, target ); 156 } 157 158 function bind( $container, track ) 159 { 160 var frame = null; 161 162 $container.on( 'click.avphonetrack', '.prev-slide, .next-slide', function( event ) 163 { 164 event.preventDefault(); 165 scroll_page( track, $( this ).hasClass( 'next-slide' ) ? 1 : -1 ); 166 } ); 167 168 track.addEventListener( 'scroll', function() 169 { 170 if( frame ) 171 { 172 return; 173 } 174 175 frame = window.requestAnimationFrame( function() 176 { 177 frame = null; 178 update_arrows( $container, track ); 179 } ); 180 }, { passive: true } ); 181 182 $( window ).on( 'debouncedresize.avphonetrack', function() 183 { 184 update_arrows( $container, track ); 185 } ); 186 } 187 188 /** 189 * Freezes a slider that is running while its element becomes a track. 190 * 191 * Nothing is unbound - the slider has no way to be taken apart, and taking it apart is not 192 * needed here. Its own guard flag stops it navigating, which is what would otherwise write 193 * inline visibility onto wrappers that no longer generate a box, and the autoplay timer is 194 * stopped so it is not ticking against a frozen slider. Both are undone in resume_slider(). 195 */ 196 function freeze_slider( instance ) 197 { 198 if( ! instance ) 199 { 200 return; 201 } 202 203 instance.isAnimating = true; 204 205 // whether it was rotating at this moment, which is not the same as whether autoplay is on: 206 // a visitor who worked the arrows has already stopped it, and it must stay stopped 207 instance.av_track_was_playing = !! instance.slideshow; 208 209 if( instance.slideshow && 'function' === typeof instance.slideshow.destroy ) 210 { 211 instance.slideshow.destroy(); 212 } 213 } 214 215 function resume_slider( instance, track ) 216 { 217 if( ! instance ) 218 { 219 return; 220 } 221 222 instance.isAnimating = false; 223 224 // while frozen it kept measuring slides that had no box, and wrote the nothing it found 225 // onto the container - which only stayed invisible because the track css outranked it 226 track.style.height = ''; 227 228 if( 'function' === typeof instance._setSize ) 229 { 230 instance._setSize( true ); 231 } 232 233 if( instance.av_track_was_playing && 'function' === typeof instance._startSlideshow ) 234 { 235 instance._startSlideshow(); 236 } 237 } 238 239 /** 240 * @param {jQuery} $container one slider element 241 */ 242 function claim( $container ) 243 { 244 var track = $container.find( '.avia-content-slider-inner' ).first()[0]; 245 246 if( ! track || $container.hasClass( CLAIMED ) ) 247 { 248 return; 249 } 250 251 // how many items the element shows per screen above the phone breakpoint - the slides the 252 // server built are the only place that number exists in the markup, and they are about to 253 // stop being slides. the element stays claimed after a rotation to landscape, so without 254 // this it would have no idea how wide to make its items there. 255 // it goes into its own property: an inline value would otherwise outrank the breakpoints 256 var per_slide = $container.find( '.slide-entry-wrap' ).first().children().length; 257 258 if( per_slide > 0 ) 259 { 260 track.style.setProperty( '--av-track-cols-default', per_slide ); 261 } 262 263 freeze_slider( $container.data( 'aviaSlider' ) ); 264 265 // dropping the active class is what keeps the slider out: it is both the init selector and 266 // the hook the css uses to stack the wrappers on top of each other 267 $container.removeClass( 'avia-content-slider-active' ).addClass( CLAIMED ); 268 269 // a scrollable region needs to be reachable for people who scroll with the keyboard 270 track.setAttribute( 'tabindex', '0' ); 271 272 // display:contents drops list semantics in some browsers - the product slides are real lists 273 $( track ).find( 'ul.products' ).attr( 'role', 'list' ); 274 275 // an element that fitted its items on one slide was rendered without arrows - it keeps its 276 // scrollbar, since nothing else would tell a visitor with a mouse that the row goes on 277 $container.toggleClass( 'av-track-no-arrows', 0 === $container.find( '.avia-slideshow-arrows a' ).length ); 278 279 bind( $container, track ); 280 update_arrows( $container, track ); 281 } 282 283 /** 284 * Hands the element back to the slider it was taken from. 285 * 286 * Only an element that had a slider is handed back. One that was claimed before any slider 287 * started keeps its track at every width - it is laid out from the element's own column count 288 * up there, which looks like the slider minus the animation, and starting a slider on it would 289 * mean waiting out the image preloader with the element already emptied. 290 */ 291 function release( $container ) 292 { 293 var track = $container.find( '.avia-content-slider-inner' ).first()[0], 294 instance = $container.data( 'aviaSlider' ); 295 296 if( ! track || ! instance || ! $container.hasClass( CLAIMED ) ) 297 { 298 return; 299 } 300 301 $container.off( '.avphonetrack' ); 302 track.removeAttribute( 'tabindex' ); 303 track.scrollLeft = 0; 304 305 $container.removeClass( CLAIMED ).addClass( 'avia-content-slider-active' ); 306 307 resume_slider( instance, track ); 308 } 309 310 function claim_all( context ) 311 { 312 if( ! is_phone() ) 313 { 314 return; 315 } 316 317 $( SLIDERS, context || document ).each( function() 318 { 319 claim( $( this ) ); 320 } ); 321 } 322 323 /**
324 * Every element this script looks after, claimed or not - the claim takes the class the 325 * elements are found by, so they have to be remembered rather than looked up again. 326 */ 327 var managed = $(); 328 329 function watch( context ) 330 { 331 managed = managed.add( $( SLIDERS, context || document ) ); 332 } 333 334 var on_phone = is_phone(); 335 336 function follow_viewport() 337 { 338 var phone = is_phone(); 339 340 if( phone === on_phone ) 341 { 342 return; 343 } 344 345 on_phone = phone; 346 347 managed.each( function() 348 { 349 var $container = $( this ); 350 351 if( phone ) 352 { 353 claim( $container ); 354 } 355 else 356 { 357 release( $container ); 358 } 359 } ); 360 } 361 362 // the scripts sit in the footer, so the markup is here and the elements can be claimed before 363 // any ready handler - including the one that starts the sliders - gets to run 364 watch( document ); 365 claim_all( document ); 366 367 // both, because neither is reliable on its own: a media query change is the precise signal but 368 // goes undelivered in some browsers when the viewport is emulated rather than really resized, 369 // and a resize is delivered everywhere but also fires while scrolling on a phone. following it 370 // costs nothing when the answer has not changed 371 if( 'function' === typeof window.matchMedia ) 372 { 373 var phone_query = window.matchMedia( QUERY ); 374 375 if( 'function' === typeof phone_query.addEventListener ) 376 { 377 phone_query.addEventListener( 'change', follow_viewport ); 378 } 379 else if( 'function' === typeof phone_query.addListener ) 380 { 381 // older browsers, safari below 14 among them 382 phone_query.addListener( follow_viewport ); 383 } 384 } 385 386 $( window ).on( 'resize.avphonetrack orientationchange.avphonetrack', follow_viewport ); 387 388 // should the order ever change, or a slider be started for content loaded later 389 if( $.fn.aviaSlider ) 390 { 391 var start_slider = $.fn.aviaSlider; 392 393 $.fn.aviaSlider = function() 394 { 395 var $unclaimed = this.not( '.' + CLAIMED ); 396 397 return $unclaimed.length ? start_slider.apply( $unclaimed, arguments ) : this; 398 }; 399 } 400 401 $( function() 402 { 403 if( ! $.avia_utilities || 'function' !== typeof $.avia_utilities.activate_shortcode_scripts ) 404 { 405 return; 406 } 407 408 var activate = $.avia_utilities.activate_shortcode_scripts; 409 410 $.avia_utilities.activate_shortcode_scripts = function( container ) 411 { 412 watch( container ); 413 claim_all( container ); 414 415 return activate.apply( this, arguments ); 416 }; 417 } ); 418 419} )( jQuery );
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.