1class CgCarousel { 2 /** 3 * Constructor 4 */ 5 constructor(selector, options = {}, hooks = []) { 6 this.container = document.querySelector(selector); 7 if (!this.container) return; 8 9 // Selectors 10 this.slidesSelector = options.slidesSelector || '.js-carousel__slide'; 11 this.trackSelector = options.trackSelector || '.js-carousel__track'; 12 this.slides = []; 13 this.track = this.container.querySelector(this.trackSelector); 14 this.slidesLength = 0; 15 16 // Breakpoints 17 this.currentBreakpoint = undefined; 18 this.breakpoints = options.breakpoints || {}; 19 20 // Hooks 21 this.hooks = hooks; 22 23 // Options 24 this.initialOptions = { 25 loop: options.loop || false, 26 autoplay: options.autoplay || false, 27 autoplaySpeed: options.autoplaySpeed || 3000, 28 transitionSpeed: options.transitionSpeed || 650, 29 slidesPerView: options.slidesPerView || 1, 30 spacing: options.spacing || 0, 31 }; 32 this.options = this.initialOptions; 33 34 // Transitions 35 this.animationStart = undefined; 36 this.animation = undefined; 37 this.animationCurrentTrans = 0; 38 this.animationIndex = 0; 39 40 // Animations 41 window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || 42 window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 43 window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; 44 45 46 // Functional 47 this.autoplayInterval = undefined; 48 this.isButtonRightDisabled = false; 49 this.isButtonLeftDisabled = false; 50 this.currentIndex = 0; 51 this.maxIndex = 0; 52 this.isInfinite = false; 53 this.isPrevInfinite = false; 54 55 // Swipe Event 56 this.swipeStartX = undefined; 57 this.swipeStartY = undefined; 58 this.swipeThreshold = 100; // Required min distance traveled to be considered swipe. 59 this.swipeRestraint = 100; // Maximum distance allowed at the same time in perpendicular direction. 60 this.swipeDir = undefined; 61 62 if (!this.track) return; 63 64 this.addEventListeners(); 65 this.initCarousel(); 66 }; 67 68 /** 69 * Fire hook. 70 */ 71 hook(name) { 72 this.hooks[name] && this.hooks[name](this); 73 }; 74 75 /** 76 * Is touchable device? 77 */ 78 isTouchableDevice() { 79 return window.matchMedia("(pointer: coarse)").matches; 80 }; 81 82 /** 83 * Handle Swipe. 84 */ 85 handleSwipe() { 86 switch (this.swipeDir) { 87 case 'top': 88 case 'bottom': 89 break; 90 case 'left': 91 this.next(); 92 break; 93 case 'right': 94 this.prev(); 95 break; 96 default: 97 break; 98 } 99 }; 100 101 /** 102 * On swipe start. 103 */ 104 onSwipeStart(e) { 105 if (!this.isTouchableDevice() || !e.changedTouches) return; 106 const touch = e.changedTouches[0]; 107 this.swipeStartX = touch.pageX; 108 this.swipeStartY = touch.pageY; 109 }; 110 111 /** 112 * Set swipe direction. 113 */ 114 setSwipeDirection(e) { 115 const touch = e.changedTouches[0]; 116 const distX = touch.pageX - this.swipeStartX; 117 const distY = touch.pageY - this.swipeStartY; 118 119 if (Math.abs(distX) >= this.swipeThreshold && Math.abs(distY) <= this.swipeRestraint) { 120 this.swipeDir = (distX < 0) ? 'left' : 'right'; 121 } else if (Math.abs(distY) >= this.swipeThreshold && Math.abs(distX) <= this.swipeRestraint) { 122 this.swipeDir = (distY < 0) ? 'up' : 'down'; 123 } 124 }; 125 126 /** 127 * On swipe move. 128 */ 129 onSwipeMove(e) { 130 if (!this.isTouchableDevice() || !e.changedTouches) return; 131 this.setSwipeDirection(e); 132 if (['left', 'right'].includes(this.swipeDir) && e.cancelable) e.preventDefault(); 133 }; 134 135 /** 136 * On swipe end. 137 */ 138 onSwipeEnd(e) { 139 if (!this.isTouchableDevice() || !e.changedTouches) return; 140 this.setSwipeDirection(e); 141 this.handleSwipe(); 142 }; 143 144 /** 145 * Add Event Listeners. 146 */ 147 addEventListeners() { 148 // Resize Events 149 window.addEventListener('orientationchange', () => this.onResize()); 150 window.addEventListener('resize', () => this.onResize()); 151 152 // Swipe Events 153 this.container.addEventListener('touchstart', (e) => this.onSwipeStart(e), { 154 passive: true 155 }); 156 this.container.addEventListener('touchmove', (e) => this.onSwipeMove(e), false); 157 this.container.addEventListener('touchend', (e) => this.onSwipeEnd(e), { 158 passive: true 159 }); 160 }; 161 162 /** 163 * On Resize Event. 164 */ 165 onResize() { 166 const breakpoint = this.checkBreakpoint(); 167 breakpoint && this.buildCarousel(); 168 this.hook('resized'); 169 }; 170 171 /** 172 * Configure Autoplay. 173 */ 174 setUpAutoplay() { 175 if (!this.options.autoplay) return; 176 clearInterval(this.autoplayInterval); 177 this.autoplayInterval = setInterval(() => this.next(), this.options.autoplaySpeed); 178 }; 179 180 /** 181 * Check current breakpoint. 182 */ 183 checkBreakpoint() { 184 if (!this.breakpoints) return; 185 186 const currentBreakpoint = Object.keys(this.breakpoints).reverse().find(breakpoint => { 187 const media = `(min-width: ${breakpoint}px)`; 188 return window.matchMedia(media).matches; 189 }); 190 191 if (this.currentBreakpoint === currentBreakpoint) return; 192 193 this.currentBreakpoint = currentBreakpoint; 194 const options = currentBreakpoint ? this.breakpoints[currentBreakpoint] : this.initialOptions; 195 this.options = { 196 ...this.initialOptions, 197 ...options 198 }; 199 return true; 200 }; 201 202 /** 203 * Set buttons visibility. 204 */ 205 setButtonsVisibility() { 206 this.isButtonLeftDisabled = !this.options.loop && this.currentIndex === 0; 207 this.isButtonRightDisabled = !this.options.loop && this.currentIndex === (this.maxIndex - 1); 208 }; 209 210 /**
211 * Clear Carousel Styles. 212 */ 213 clearCarouselStyles() { 214 const trackStyles = ['grid-auto-columns', 'gap', 'transition', 'left']; 215 trackStyles.map(style => this.track.style.removeProperty(style)); 216 const slideStyles = ['grid-column-start', 'grid-column-end', 'grid-row-start', 'grid-row-end', 'left']; 217 this.slides.forEach(slide => { 218 slideStyles.map(style => slide.style.removeProperty(style)); 219 }); 220 }; 221 222 /** 223 * Set Carousel Classes. 224 */ 225 setCarouselStyles() { 226 if (!this.slides) return; 227 const slidesPerView = this.options.slidesPerView; 228 const slideWidth = 100 / slidesPerView; 229 const gap = (this.options.spacing * (slidesPerView - 1)) / slidesPerView; 230 this.track.style.gridAutoColumns = `calc(${slideWidth}% - ${gap}px)`; 231 this.track.style.gridGap = `${this.options.spacing}px`; 232 this.track.style.left = 0; 233 }; 234 235 /** 236 * Build Carousel with current Options. 237 */ 238 buildCarousel() { 239 this.maxIndex = Math.ceil(this.slidesLength / this.options.slidesPerView); 240 this.clearCarouselStyles(); 241 this.setCarouselStyles(); 242 this.setButtonsVisibility(); 243 this.setUpAutoplay(); 244 this.currentIndex = 0; 245 this.hook('built'); 246 }; 247 248 /** 249 * Initialize carousel and fire created event. 250 */ 251 initCarousel() { 252 this.slides = this.container.querySelectorAll(this.slidesSelector); 253 this.slidesLength = this.slides?.length; 254 this.checkBreakpoint(); 255 this.buildCarousel(); 256 this.hook('created'); 257 }; 258 259 /** 260 * On Animation End. 261 */ 262 onAnimationEnd() { 263 const gap = this.options.spacing * this.animationIndex; 264 const trans = this.animationIndex * -100; 265 this.track.style.left = `calc(${trans}% - ${gap}px)`; 266 this.animationCurrentTrans = trans; 267 this.animation = null; 268 this.isInfinite && this.clearInfinite(); 269 this.isPrevInfinite && this.clearPrevInfinite(); 270 } 271 272 /** 273 * Cancel Animation frame. 274 */ 275 moveAnimateAbort() { 276 if (!this.animation) return; 277 278 window.cancelAnimationFrame(this.animation); 279 this.onAnimationEnd(); 280 }; 281 282 /** 283 * Animate Left. 284 * @param timestamp 285 * @param trans 286 * @param gap 287 * @param duration 288 */ 289 animateLeft(timestamp, trans, gap, duration) { 290 const runtime = timestamp - this.animationStart; 291 const easing = t => 1 - Math.pow(1 - t, 5); 292 const progress = easing(runtime / duration); 293 const dist = ((trans * progress) + (this.animationCurrentTrans * (1 - progress))).toFixed(2); 294 this.track.style.left = `calc(${dist}% - ${gap}px)`; 295 296 if (runtime >= duration) { 297 this.onAnimationEnd(); 298 return; 299 } 300 301 this.animation = window.requestAnimationFrame(timestamp => { 302 this.animateLeft(timestamp, trans, gap, duration) 303 }) 304 }; 305 306 /** 307 * Move Slide. 308 */ 309 moveSlide(index, cIndex) { 310 this.moveAnimateAbort(); 311 const gap = this.options.spacing * index; 312 const trans = index * -100; 313 314 this.animation = window.requestAnimationFrame((timestamp) => { 315 index === this.maxIndex && this.setInfinite(); 316 index === -1 && this.setPrevInfinite(); 317 this.animationStart = timestamp; 318 this.animationIndex = this.currentIndex; 319 this.animateLeft(timestamp, trans, gap, this.options.transitionSpeed); 320 }); 321 322 this.currentIndex = cIndex; 323 324 this.setUpAutoplay(); 325 this.setButtonsVisibility(); 326 this.hook('moved'); 327 }; 328 329 /** 330 * Set Infinity. 331 */ 332 setInfinite() { 333 this.isInfinite = true; 334 const count = this.options.slidesPerView * this.maxIndex; 335 336 for (let idx = 0; idx < this.options.slidesPerView; idx++) { 337 const slide = this.slides[idx]; 338 slide.style.left = `calc((100% * ${count}) + (${this.options.spacing}px * ${count}))`; 339 } 340 }; 341 342 /** 343 * Clear Infinity. 344 */ 345 clearInfinite() { 346 this.isInfinite = false; 347 this.track.style.left = `calc(${this.currentIndex * -100}% - ${this.options.spacing * this.currentIndex}px)`; 348
349 this.slides.forEach((slide, idx) => { 350 if (idx >= this.options.slidesPerView) return; 351 slide.style.removeProperty('left'); 352 }) 353 }; 354 355 /** 356 * Next Slide. 357 */ 358 next() { 359 const nextIndex = this.currentIndex === this.maxIndex - 1 ? 0 : this.currentIndex + 1; 360 if (!this.options.loop && nextIndex < this.currentIndex) return; 361 362 if (nextIndex < this.currentIndex) { 363 this.moveSlide(this.currentIndex + 1, nextIndex); 364 return; 365 } 366 367 this.moveSlide(nextIndex, nextIndex); 368 }; 369 370 /** 371 * Set Prev Infinity. 372 */ 373 setPrevInfinite() { 374 this.isPrevInfinite = true; 375 const count = this.options.slidesPerView * this.maxIndex; 376 const maxIdx = count - this.options.slidesPerView; 377 378 for (let idx = this.slides.length - 1; idx >= 0; idx--) { 379 if (idx < maxIdx) return; 380 const slide = this.slides[idx]; 381 slide.style.left = `calc((-100% * ${count}) - (${this.options.spacing}px * ${count}))`; 382 } 383 }; 384 385 /** 386 * Clear Prev Infinity. 387 */ 388 clearPrevInfinite() { 389 this.isPrevInfinite = false; 390 this.track.style.left = `calc(${this.currentIndex * -100}% - ${this.options.spacing * this.currentIndex}px)`; 391 this.slides.forEach((slide, idx) => { 392 slide.style.removeProperty('left'); 393 }); 394 }; 395 396 /** 397 * Prev Slide. 398 */ 399 prev() { 400 const nextIndex = this.currentIndex === 0 ? this.maxIndex - 1 : this.currentIndex - 1; 401 if (!this.options.loop && nextIndex > this.currentIndex) return; 402 403 if (nextIndex > this.currentIndex) { 404 this.moveSlide(this.currentIndex - 1, nextIndex); 405 return; 406 } 407 408 this.moveSlide(nextIndex, nextIndex); 409 }; 410 411 /** 412 * Move to Slide. 413 */ 414 moveToSlide(index) { 415 if (index === this.currentIndex) return; 416 this.moveSlide(index, index); 417 }; 418 419 /** 420 * Get Slides 421 */ 422 getSlides() { 423 return this.slides; 424 }; 425 426 /** 427 * Get Current Index. 428 */ 429 getCurrentIndex() { 430 return this.currentIndex; 431 }; 432 433 /** 434 * Get Options. 435 */ 436 getOptions() { 437 return this.options; 438 }; 439 440 /** 441 * Get Page Size. 442 */ 443 getPageSize() { 444 return this.maxIndex; 445 }; 446} 447 448window.CgCarousel = CgCarousel;
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.