1/* 2* A simple vanilla JS fader with infinite looping and play/pause button 3* 4* @param string id the id of the fader element 5* @param int interval the time in milliseconds before fading next slide 6*/ 7 8class WS_Fader { 9 10 button = null; 11 clicked = false; 12 clones_made = false; 13 counter = 0; 14 cur_dot = null; 15 cur_slide = null; 16 dots = null; 17 prev = null; 18 slide_count = 0; 19 slides = null; 20 timer = null; 21 22 23 constructor( args ) { 24 this.id = args['id']; 25 this.interval = args['interval']; 26 this.show_dots = args['dots']; 27 this.show_button = args['button']; 28 this.normalize = args['normalize']; 29 30 this.initialize(); 31 } 32 33 34 /* 35 PERFORM SOME STARTING CHECKS 36 */ 37 initialize = () => { 38 39 this.count_slides(); 40 41 // If there are no slides, bail 42 if ( 0 == this.slide_count ) { 43 return; 44 } 45 46 // If there's only one slide, set its opacity to 1, hide the dots and pause button, then bail 47 if ( 1 == this.slide_count ) { 48 this.slides[0].style.opacity = 1; 49 this.slides[0].style.position = 'static'; 50 return; 51 } 52 53 // If there are only 2 slides, clone them, then continue 54 if ( 2 == this.slide_count ) { 55 let clone_one = this.slides[0].cloneNode( true ); 56 let clone_two = this.slides[1].cloneNode( true ); 57 clone_one.classList.remove('on'); 58 59 this.slides[0].after( clone_one ); 60 this.slides[0].after( clone_two ); 61 62 this.clones_made = true; 63 64 this.count_slides(); 65 } 66 67 // Create dots if show_dots == true, there's more than 1 slide, and clones weren't made 68 if ( this.show_dots && this.slide_count > 1 && !this.clones_made) { 69 this.create_dots(); 70 } 71 72 // Create pause button if show_button == true and there's more than 1 slide 73 if ( this.show_button && this.slide_count > 1 ) { 74 this.create_button(); 75 } 76 77 // Start the fader 78 this.start(); 79 } // initialize() 80 81 82 /* 83 COUNT THE NUMBER OF SLIDES 84 */ 85 count_slides = () => { 86 this.slides = document.querySelectorAll( this.id + '>*:not(.dots):not(button)' ); 87 this.slide_count = this.slides.length; 88 } 89 90 91 /* 92 SET INITIAL 'on' AND 'prev' SLIDE CLASSES, 'active' INDICATOR DOT, THEN SET FADER TIMER 93 */ 94 start = () => { 95 96 // Set first slide 97 this.cur_slide = this.slides[0]; 98 this.cur_slide.classList.add( 'on' ); 99 100 // Set previous slide 101 this.prev = this.slides[this.slide_count-1]; 102 this.prev.classList.add( 'prev' ); 103 104 // Set first dot 105 if ( this.show_dots && !this.clones_made ) { 106 this.cur_dot = this.dots[0]; 107 this.cur_dot.classList.add( 'active' ); 108 } 109 110 // Normalize slide heights 111 if ( this.normalize ) { 112 this.normalize_heights(); 113 } 114 115 // Start fader 116 this.fade(); 117 this.timer = setInterval( this.fade, this.interval ); 118 } // start() 119 120 121 /* 122 TOGGLE FADE CLASSES EVERY [INTERVAL] MILLISECONDS 123 */ 124 fade = () => { 125 126 // Advance slide 127 this.cur_slide.classList.remove( 'on' ); 128 this.cur_slide = this.slides[this.counter]; 129 this.cur_slide.classList.add( 'on' ); 130 131 this.prev.classList.remove('prev'); 132 this.prev = this.slides[this.counter-1] ? this.slides[this.counter-1] : this.slides[this.slide_count-1]; 133 this.prev.classList.add( 'prev' ); 134 135 // Advance dot 136 if ( this.show_dots && !this.clones_made ) { 137 this.cur_dot.classList.remove( 'active' ); 138 this.cur_dot = this.dots[this.counter]; 139 this.cur_dot.classList.add( 'active' ); 140 } 141 142 // If slide was advanced in response to a dot click, reset the cleared timer 143 if ( this.clicked ) { 144 this.clicked = false; 145 this.timer = setInterval( this.fade, this.interval ); 146 } 147 148 // Increment counter for next slide 149 this.counter = (this.counter + 1 == this.slide_count) ? 0 : this.counter + 1; 150 151 } // fade() 152 153 154 155 /* 156 NORMALIZE SLIDE HEIGHTS 157 */ 158 normalize_heights = () => { 159 let heights = [], tallest; 160 this.slides.forEach( element => { 161 heights.push(element.offsetHeight); 162 }); 163 tallest = Math.max.apply( null, heights );
164 this.slides.forEach( element => { 165 element.style.height = tallest + 'px'; 166 }); 167 } 168 169 170 171 /* 172 CREATE SLIDE INDICATOR DOTS 173 */ 174 create_dots = () => { 175 176 let indicator_list = document.createElement('ul'); 177 indicator_list.classList.add( 'dots' ); 178 179 for( let i=0; i<this.slide_count; i++ ) { 180 let new_dot = document.createElement('li'); 181 let new_button = document.createElement('button'); 182 new_dot.appendChild(new_button); 183 indicator_list.appendChild(new_dot); 184 } 185 186 // Add UL element to fader element 187 document.querySelector( this.id ).appendChild( indicator_list ); 188 189 // Bind indicator dots event listener 190 document.querySelectorAll( this.id + ' ul.dots>li>button' ).forEach(button => { 191 button.addEventListener( 'click', this.dot_click ); 192 }); 193 194 this.dots = document.querySelectorAll( this.id + '>ul>li' ); 195 196 } 197 198 199 /* 200 HANDLE INDICATOR DOT CLICK 201 */ 202 dot_click = (e) => { 203 e.target.blur(); 204 205 // Get index of indicator dot that was clicked 206 let all_dots = document.querySelectorAll( this.id + ' ul.dots>li' ); 207 let clicked_dot = e.target.parentNode; 208 let click_index = Array.prototype.indexOf.call( all_dots, clicked_dot ); 209 210 // Set counter to clicked dot index 211 this.counter = click_index; 212 213 // Set click flag to true, clear timer, force slide change 214 this.clicked = true; 215 clearInterval( this.timer ); 216 217 // Unpause play/pause button if it was paused 218 if( this.show_button) { 219 this.button.classList.remove( 'paused' ); 220 } 221 222 // Trigger fade 223 this.fade(); 224 } 225 226 227 /* 228 CREATE PAUSE BUTTON AND BIND CLICK EVENT LISTENER TO IT 229 */ 230 create_button = () => { 231 let pause_button = document.createElement( 'button' ); 232 pause_button.innerHTML = '<span class="sr-only">Pause image rotator</span>'; 233 pause_button.setAttribute( 'aria-live', 'polite' ); 234 235 document.querySelector( this.id ).appendChild( pause_button ); 236 237 this.button = document.querySelector( this.id + '>button' ); 238 this.button?.addEventListener( 'click', this.pause ); 239 } 240 241 242 /* 243 HANDLE PAUSE BUTTON PRESS 244 */ 245 pause = () => { 246 this.button.classList.toggle( 'paused' ); 247 248 if( this.button.classList.contains( 'paused' ) ) { 249 clearInterval( this.timer ); 250 this.button.querySelector('span.sr-only').textContent = 'Play image rotator'; 251 return; 252 } 253 254 this.timer = setInterval( this.fade, this.interval ); 255 this.button.querySelector('span.sr-only').textContent = 'Pause image rotator'; 256 } // pause() 257 258}
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.