1const selectors = { 2 BUTTON_HIDDEN: 'hb-references__button--hidden', 3 DOTS: 'hb-references__dots', 4 DOT: 'hb-references__dot', 5 DOT_ACTIVE: 'hb-references__dot--active', 6} 7 8document.addEventListener('DOMContentLoaded', () => 9 document.querySelectorAll('[data-references]') 10 .forEach(el => initialize(el))); 11 12function initialize(rootEl) { 13 const CarousePositionPropertyName = '--carouselPosition'; 14 15 const slidesEl = rootEl.querySelector('[data-references-slides]'); 16 const previousButtonEl = rootEl.querySelector('[data-references-button="previous"]'); 17 const nextButtonEl = rootEl.querySelector('[data-references-button="next"]'); 18 if (slidesEl === null || previousButtonEl === null || nextButtonEl === null) { 19 return; 20 } 21 22 const slidesCount = slidesEl.children.length; 23 const defaultPosition = parseInt(window.getComputedStyle(slidesEl).getPropertyValue(CarousePositionPropertyName)); 24 const allowInfinite = typeof rootEl.dataset.referencesInfinite !== 'undefined'; 25 const allowAutoplay = typeof rootEl.dataset.referencesAutoplay !== 'undefined'; 26 const noDots = typeof rootEl.dataset.referencesNoDots !== 'undefined'; 27 28 const elementsInView = new ElementsInView(slidesEl); 29 const position = new Position(slidesCount, defaultPosition, allowInfinite, elementsInView); 30 const dots = noDots 31 ? DotsFactory.disabled() // todo listen to elements in view, display only if count == 1 32 : DotsFactory.enabled(slidesEl, position, slidesCount); 33 const autoplay = new Autoplay(position); 34 35 position.addPositionChangedSubscriber((newPosition) => { 36 slidesEl.style.setProperty(CarousePositionPropertyName, newPosition.toString()); 37 updateButtonVisibility(); 38 dots.repaint(newPosition); 39 }); 40 41 previousButtonEl.addEventListener('click', () => { 42 if (position.isAtFirst()) { 43 return; 44 } 45 46 autoplay.disable(); 47 position.moveToPrevious(); 48 }); 49 50 nextButtonEl.addEventListener('click', () => { 51 if (position.isAtLast()) { 52 return; 53 } 54 55 autoplay.disable(); 56 position.moveToNext(); 57 }); 58 59 const updateButtonVisibility = () => { 60 previousButtonEl.classList.remove(selectors.BUTTON_HIDDEN); 61 nextButtonEl.classList.remove(selectors.BUTTON_HIDDEN); 62 63 if (position.isAtFirst()) { 64 previousButtonEl.classList.add(selectors.BUTTON_HIDDEN); 65 } 66 67 if (position.isAtLast()) { 68 nextButtonEl.classList.add(selectors.BUTTON_HIDDEN); 69 } 70 }; 71 72 // todo: resize, orientationchange 73 74 75 // on init 76 updateButtonVisibility(); 77 dots.repaint(defaultPosition); 78 79 if (allowAutoplay) { 80 autoplay.enable(); 81 } 82} 83 84 85/* ELEMENTS IN VIEW */ 86 87class ElementsInView { 88 89 /** @private */ 90 slidesEl; 91 92 constructor(slidesEl) { 93 this.slidesEl = slidesEl; 94 } 95 96 count() { 97 const containerWidth = this.slidesEl.getBoundingClientRect().width; 98 const slideWidth = this.slidesEl.children[0].getBoundingClientRect().width; 99 const ratio = containerWidth / slideWidth; 100 const count = ratio < 1 ? 1 : Math.floor(ratio); 101 return count; 102 } 103 104} 105 106 107/* POSITION */ 108 109class Position { 110 /** @private */ 111 slidesCount; // number 112 currentPosition; // number 113 /** @private */ 114 allowInfinite; // boolean 115 /** @private */ 116 elementsInView; 117 118 constructor(slidesCount, defaultPosition, allowInfinite, elementsInView) { 119 this.slidesCount = slidesCount; 120 this.currentPosition = defaultPosition; 121 this.allowInfinite = allowInfinite; 122 this.elementsInView = elementsInView; 123 } 124 125 moveToNext() { 126 if (this.currentPosition === this.getLastPosition()) { 127 this.currentPosition = 0; 128 } else { 129 this.currentPosition++; 130 } 131 132 this.positionChangedSubscribers.forEach(subscriber => subscriber(this.currentPosition)); 133 } 134 135 moveToPrevious() { 136 if (this.currentPosition === 0) { 137 this.currentPosition = this.getLastPosition(); 138 } else { 139 this.currentPosition--; 140 } 141 142 this.positionChangedSubscribers.forEach(subscriber => subscriber(this.currentPosition)); 143 } 144 145 moveTo(position) { 146 if (position < 0 || position > (this.slidesCount - 1)) { 147 throw new Error('Position out of range'); 148 } 149 150 const lastPosition = this.getLastPosition() 151 this.currentPosition = position > lastPosition 152 ? lastPosition 153 : position; 154
155 this.positionChangedSubscribers.forEach(subscriber => subscriber(this.currentPosition)); 156 } 157 158 /** @private */ 159 positionChangedSubscribers = []; 160 161 addPositionChangedSubscriber(callback) { 162 this.positionChangedSubscribers.push(callback); 163 } 164 165 isAtFirst() { 166 if (this.allowInfinite) { 167 return false; 168 } 169 170 return this.currentPosition === 0; 171 } 172 173 isAtLast() { 174 if (this.allowInfinite) { 175 return false; 176 } 177 178 const lastPosition = this.getLastPosition(); 179 return this.currentPosition >= lastPosition; 180 } 181 182 /** @private */ 183 getLastPosition() 184 { 185 /** 186 * example: 187 * slides at once last position (we always want to render whole group) 188 * 5 1 4 189 * 5 2 3 190 * 5 3 2 191 * 5 4 1 192 * 5 5 0 193 */ 194 return this.slidesCount - this.elementsInView.count(); 195 } 196} 197 198 199/* DOTS */ 200 201/** 202@interface Dots { 203 repaint(newPosition: number): void, 204} 205*/ 206 207class DotsFactory 208{ 209 static enabled(dataStorageEl, position, totalSlides) { 210 return new OnePerSlide(dataStorageEl, position, totalSlides); 211 } 212 213 static disabled() { 214 return new Noop(); 215 } 216} 217 218/** @implements Dots */ 219class OnePerSlide { 220 /** @private */ 221 dots = []; 222 /** @private */ 223 position; 224 225 constructor(dataStorageEl, position, totalSlides) { 226 this.position = position; 227 228 const containerEl = document.createElement('div'); 229 containerEl.classList.add(selectors.DOTS); 230 231 for (let i = 0; i < totalSlides; i++) { 232 this.dots.push(new Dot(containerEl, this.position, i)); 233 } 234 235 dataStorageEl.insertAdjacentElement("afterend", containerEl); 236 } 237 238 repaint(newPosition) { 239 this.dots.forEach(dot => dot.repaint(newPosition)); 240 } 241} 242 243/** 244 * When no dots are needed 245 * @implements Dots 246 */ 247class Noop { 248 constructor() {} 249 repaint(newPosition) {} 250} 251 252class Dot 253{ 254 /** @private @readonly */ 255 el; 256 /** @private */ 257 position; 258 /** @private */ 259 offset; 260 261 constructor(containerEl, position, offset) { 262 this.position = position; 263 this.offset = offset; 264 265 this.el = document.createElement('div'); 266 this.el.classList.add(selectors.DOT); 267 containerEl.appendChild(this.el); 268 269 this.el.addEventListener('click', () => position.moveTo(this.offset)); 270 } 271 272 repaint(newPosition) { 273 if (this.offset === newPosition) { 274 this.el.classList.add(selectors.DOT_ACTIVE); 275 } else { 276 this.el.classList.remove(selectors.DOT_ACTIVE); 277 } 278 } 279} 280 281 282/* AUTOPLAY */ 283 284class Autoplay { 285 /** @private */ 286 intervalId = null; 287 /** @private */ 288 seconds = 5; 289 /** @private */ 290 position; 291 292 constructor(position) { 293 this.position = position; 294 } 295 296 enable() { 297 this.intervalId = window.setInterval( 298 () => this.position.moveToNext(), 299 this.seconds * 1000, 300 ); 301 } 302 303 disable() { 304 if (this.intervalId === null) { 305 return; 306 } 307 308 window.clearInterval(this.intervalId); 309 this.intervalId = null; 310 } 311}
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.