1class Carousel { 2 element = null; 3 buttonDisabled = false; 4 nav = null; 5 items = []; 6 indicators = []; // for storing indicators 7 size = 2.5; // number of items to show 8 gap = 16; // in px 9 activeClass = true; 10 itemProps = { 11 width: 0, 12 left: 0 13 }; 14 currentIndex = 0; // To track the currently visible item 15 16 constructor(element) { 17 if (!element) throw new Error("Carousel element not found"); 18 this.element = element; 19 20 // Initialize collections 21 this.refreshItems(); 22 this.nav = element.parentElement.querySelectorAll(".carousel__nav__item"); 23 this.indicators = element.parentElement.querySelectorAll(".carousel__indicator"); 24 25 // Initialize first indicator if they exist 26 if (this.indicators.length > 0) { 27 this.indicators[0]?.classList.add("carousel__indicator--active"); 28 } 29 30 this.init(); 31 } 32 33 // Refresh the items collection 34 refreshItems() { 35 this.items = this.element.querySelectorAll(".carousel__item"); 36 } 37 38 async init() { 39 await this.setMinItems();
40 this.itemProps.width = await this.getSize(); 41 42 // Build the carousel first to ensure items are properly sized 43 await this.build(); 44 45 // Set carousel height based on first item 46 if (this.items.length > 0) { 47 this.element.style.height = this.items[0].clientHeight + "px"; 48 } 49 50 // Add event listeners to navigation buttons 51 for (let i = 0; i < this.nav.length; i++) { 52 let currentNavElement = this.nav[i]; 53 this.nav[i].addEventListener("click", () => 54 this.moveHandler(currentNavElement) 55 ); 56 } 57 58 // Add event listeners to indicators 59 for (let i = 0; i < this.indicators.length; i++) { 60 this.indicators[i].addEventListener("click", () => 61 this.indicatorClickHandler(i) 62 ); 63 } 64 65 // Add mouse move event listener for scrolling 66 this.element.addEventListener("mousemove", (event) => this.onMouseMove(event)); 67 68 this.updateIndicators(0); // Initialize indicators 69 this.nav = this.element.parentElement.querySelector(".carousel__nav"); 70 } 71 72 async setMinItems() { 73 const minItems = this.size + 2; 74 if (this.items.length < minItems) { 75 let currentLength = this.items.length; 76 for (let i = 0; i < currentLength; i++) { 77 let clonedItem = this.items[i].cloneNode(true); 78 this.element.append(clonedItem); 79 } 80 this.refreshItems(); 81 } 82 } 83 84 async getSize() { 85 let totalSpacing = this.gap * (this.size - 1); 86 let width = this.element.clientWidth - totalSpacing; 87 width = width / this.size; 88 return width; 89 } 90 91 async build() { 92 for (let i = 0; i < this.items.length; i++) { 93 this.items[i].style.width = this.itemProps.width + "px"; 94 this.items[i].style.left = 95 (this.itemProps.width + this.gap) * (i - 1) + "px"; 96 } 97 if (this.activeClass) { 98 this.setActiveClass(); 99 } 100 } 101 102 async move(pos) { 103 this.refreshItems(); // Ensure we have current items 104 105 if (this.items.length === 0) return; // Exit if no items 106 107 let item = pos === "next" ? this.items[0] : this.items[this.items.length - 1]; 108 if (!item) return; // Exit if item not found 109 110 let clonedItem = item.cloneNode(true); 111 112 if (pos === "next") { 113 this.element.append(clonedItem); 114 this.updateIndicators(1); // Move to next indicator 115 this.currentIndex = (this.currentIndex + 1) % this.indicators.length; 116 } else { 117 this.element.prepend(clonedItem); 118 this.updateIndicators(-1); // Move to previous indicator 119 this.currentIndex = (this.currentIndex - 1 + this.indicators.length) % this.indicators.length; 120 } 121 122 item.remove(); 123 this.refreshItems(); 124 this.build(); 125 } 126 127 async next() { 128 await this.move("next"); 129 } 130 131 async prev() { 132 await this.move("prev"); 133 } 134 135 async setActiveClass() { 136 let mean = Math.round(this.size / 2); 137 for (let i = 0; i < this.items.length; i++) { 138 this.items[i].classList.remove("carousel__item--active"); 139 if (i === mean) { 140 this.items[i].classList.add("carousel__item--active"); 141 } 142 } 143 } 144 145 async moveHandler(element) { 146 if (!this.buttonDisabled) { 147 this.buttonDisabled = true; 148 let direction = element.getAttribute("data-direction"); 149 if (direction === "next") { 150 await this.next(); 151 } else { 152 await this.prev(); 153 } 154 setTimeout(() => (this.buttonDisabled = false), 800); 155 } 156 } 157 158 updateIndicators(direction) { 159 if (!this.indicators || this.indicators.length === 0) return; 160 161 // Remove active class from all indicators 162 this.indicators.forEach(indicator => { 163 indicator.classList.remove("carousel__indicator--active"); 164 }); 165 166 // Calculate new active index 167 let activeIndex; 168 if (direction === 1) { 169 activeIndex = (this.currentIndex + 1) % this.indicators.length; 170 } else if (direction === -1) { 171 activeIndex = (this.currentIndex - 1 + this.indicators.length) % this.indicators.length; 172 } else { 173 activeIndex = this.currentIndex; 174 } 175 176 // Set new active indicator 177 this.indicators[activeIndex]?.classList.add("carousel__indicator--active"); 178 this.currentIndex = activeIndex; 179 } 180 181 indicatorClickHandler(index) { 182 const steps = index - this.currentIndex; 183 if (steps === 0) return; 184 185 const direction = steps > 0 ? "next" : "prev"; 186 const count = Math.abs(steps); 187 188 const moveSteps = async () => { 189 for (let i = 0; i < count; i++) { 190 await this.move(direction); 191 } 192 }; 193 194 moveSteps(); 195 } 196 197 onMouseMove(event) { 198 const rect = this.element.getBoundingClientRect(); 199 const mouseX = event.clientX - rect.left; 200 const threshold = rect.width * 0.1; 201 202 if (mouseX < threshold) { 203 this.move("prev"); 204 } else if (mouseX > rect.width - threshold) { 205 this.move("next"); 206 } 207 } 208} 209 210// Initialize carousel if element exists 211document.addEventListener("DOMContentLoaded", () => { 212 const element = document.querySelector(".carousel"); 213 if (element) { 214 new Carousel(element); 215 } 216});
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.