1/** 2 * Carrusel Moderno Custom - Standard LP Motor 3 * @author Pxsol Development Team 4 */ 5 6class PxGallery { 7 constructor(items) { 8 this.items = items; // Array of objects {src, thumb, alt} 9 this.currentIndex = 0; 10 this.overlay = null; 11 this.isZoomed = false; 12 this.scale = 1; 13 this.lastScale = 1; 14 this.panning = false; 15 this.pointX = 0; 16 this.pointY = 0; 17 this.startPointX = 0; 18 this.startPointY = 0; 19 this.lastPointX = 0; 20 this.lastPointY = 0; 21 22 this.init(); 23 } 24 25 init() { 26 this.createOverlay(); 27 this.bindEvents(); 28 } 29 30 createOverlay() { 31 // Prevent duplicate overlays 32 if (document.querySelector(".px-gallery-overlay")) return; 33 34 const div = document.createElement("div"); 35 div.className = "px-gallery-overlay"; 36 div.innerHTML = ` 37 <div class="px-gallery-header"> 38 <div class="px-gallery-counter"></div> 39 <button class="px-gallery-close" aria-label="Cerrar">×</button> 40 </div> 41 <div class="px-gallery-main"> 42 <div class="px-gallery-slides"></div> 43 <button class="px-gallery-nav prev" aria-label="Anterior">❮</button> 44 <button class="px-gallery-nav next" aria-label="Siguiente">❯</button> 45 <div class="px-gallery-loader"></div> 46 </div> 47 <div class="px-gallery-thumbnails"></div> 48 <div class="px-gallery-zoom-hint">Haga doble clic para hacer zoom</div> 49 `; 50 document.body.appendChild(div); 51 this.overlay = div; 52 53 this.slidesContainer = div.querySelector(".px-gallery-slides"); 54 this.thumbsContainer = div.querySelector(".px-gallery-thumbnails"); 55 this.counter = div.querySelector(".px-gallery-counter"); 56 this.loader = div.querySelector(".px-gallery-loader"); 57 58 // Create thumbnails 59 this.renderThumbnails(); 60 } 61 62 renderThumbnails() { 63 if (!this.thumbsContainer) return; 64 this.thumbsContainer.innerHTML = ""; 65 this.items.forEach((item, idx) => { 66 const thumb = document.createElement("div"); 67 thumb.className = "px-gallery-thumb"; 68 const image = document.createElement("img"); 69 image.src = item.thumb; 70 image.alt = item.alt || ""; 71 thumb.appendChild(image); 72 thumb.onclick = (e) => { 73 e.stopPropagation(); 74 this.goTo(idx); 75 }; 76 this.thumbsContainer.appendChild(thumb); 77 }); 78 } 79 80 bindEvents() { 81 if (!this.overlay) return; 82 this.overlay.querySelector(".px-gallery-close").onclick = () => 83 this.close(); 84 this.overlay.querySelector(".px-gallery-nav.prev").onclick = (e) => { 85 e.stopPropagation(); 86 this.prev(); 87 }; 88 this.overlay.querySelector(".px-gallery-nav.next").onclick = (e) => { 89 e.stopPropagation(); 90 this.next(); 91 }; 92 93 // Keyboard nav 94 document.addEventListener("keydown", (e) => { 95 if (!this.overlay.classList.contains("active")) return; 96 if (e.key === "Escape") this.close(); 97 if (e.key === "ArrowLeft") this.prev(); 98 if (e.key === "ArrowRight") this.next(); 99 }); 100 101 // Touch/Swipe/Zoom logic 102 const main = this.overlay.querySelector(".px-gallery-main"); 103 104 main.addEventListener("touchstart", (e) => this.handleTouchStart(e), { 105 passive: false, 106 }); 107 main.addEventListener("touchmove", (e) => this.handleTouchMove(e), { 108 passive: false, 109 }); 110 main.addEventListener("touchend", (e) => this.handleTouchEnd(e)); 111 112 // Mouse drag only when zoomed 113 main.addEventListener("mousedown", (e) => this.handleMouseDown(e)); 114 main.addEventListener("mousemove", (e) => this.handleMouseMove(e)); 115 main.addEventListener("mouseup", (e) => this.handleMouseUp(e)); 116 117 // Double tap/click to zoom 118 let lastTap = 0; 119 main.addEventListener("click", (e) => { 120 // If clicking navigating arrows, ignore 121 if (e.target.closest(".px-gallery-nav")) return; 122 123 const curTime = new Date().getTime(); 124 const tapLen = curTime - lastTap; 125 if (tapLen < 300 && tapLen > 0) { 126 // Double tap 127 this.toggleZoom(e); 128 e.preventDefault(); 129 } 130 lastTap = curTime; 131 }); 132 133 // Wheel to zoom 134 main.addEventListener( 135 "wheel", 136 (e) => { 137 if (e.ctrlKey) { 138 e.preventDefault(); 139 const delta = e.deltaY > 0 ? 0.9 : 1.1; 140 this.updateZoom(this.scale * delta); 141 } 142 }, 143 { passive: false } 144 ); 145 } 146 147 open(index = 0) { 148 if (!this.overlay) this.init(); // Re-init if needed? 149 150 this.overlay.classList.add("active"); 151 document.body.style.overflow = "hidden"; 152 this.goTo(index); 153 154 // Show hint momentarily 155 const hint = this.overlay.querySelector(".px-gallery-zoom-hint"); 156 hint.classList.add("visible"); 157 setTimeout(() => hint.classList.remove("visible"), 3000); 158 } 159 160 close() { 161 this.overlay.classList.remove("active"); 162 document.body.style.overflow = ""; 163 this.slidesContainer.innerHTML = ""; 164 this.isZoomed = false;
165 this.scale = 1; 166 } 167 168 goTo(index) { 169 if (index < 0) index = this.items.length - 1; 170 if (index >= this.items.length) index = 0; 171 172 this.currentIndex = index; 173 this.updateCounter(); 174 this.updateThumbnails(); 175 this.renderSlide(); 176 177 // Reset zoom 178 this.scale = 1; 179 this.pointX = 0; 180 this.pointY = 0; 181 this.isZoomed = false; 182 } 183 184 prev() { 185 this.goTo(this.currentIndex - 1); 186 } 187 188 next() { 189 this.goTo(this.currentIndex + 1); 190 } 191 192 updateCounter() { 193 this.counter.textContent = `${this.currentIndex + 1} / ${ 194 this.items.length 195 }`; 196 } 197 198 updateThumbnails() { 199 const thumbs = this.thumbsContainer.querySelectorAll(".px-gallery-thumb"); 200 thumbs.forEach((t, i) => { 201 if (i === this.currentIndex) { 202 t.classList.add("active"); 203 t.scrollIntoView({ 204 behavior: "smooth", 205 inline: "center", 206 block: "nearest", 207 }); 208 } else { 209 t.classList.remove("active"); 210 } 211 }); 212 } 213 214 renderSlide() { 215 const item = this.items[this.currentIndex]; 216 217 // Clear current 218 this.slidesContainer.innerHTML = ""; 219 this.loader.style.display = "block"; 220 221 const wrapper = document.createElement("div"); 222 wrapper.className = "px-gallery-image-wrapper active-slide"; 223 224 const img = new Image(); 225 img.className = "px-gallery-image"; 226 img.alt = item.alt || ""; 227 img.onload = () => { 228 this.loader.style.display = "none"; 229 img.classList.remove("loading"); 230 }; 231 img.src = item.src; 232 233 wrapper.appendChild(img); 234 this.slidesContainer.appendChild(wrapper); 235 this.currentWrapper = wrapper; 236 this.currentImage = img; 237 } 238 239 // Touch/Zoom Logic 240 handleTouchStart(e) { 241 if (e.touches.length === 2) { 242 // Pinch start 243 this.isZooming = true; 244 this.startDist = Math.hypot( 245 e.touches[0].pageX - e.touches[1].pageX, 246 e.touches[0].pageY - e.touches[1].pageY 247 ); 248 this.startScale = this.scale; 249 } else if (e.touches.length === 1) { 250 // Drag start 251 this.isDragging = true; 252 this.startX = e.touches[0].clientX; 253 this.startY = e.touches[0].clientY; 254 this.lastPointX = this.pointX; 255 this.lastPointY = this.pointY; 256 } 257 } 258 259 handleTouchMove(e) { 260 if (this.isZooming && e.touches.length === 2) { 261 e.preventDefault(); 262 const dist = Math.hypot( 263 e.touches[0].pageX - e.touches[1].pageX, 264 e.touches[0].pageY - e.touches[1].pageY 265 ); 266 const delta = dist / this.startDist; 267 this.updateZoom(this.startScale * delta); 268 } else if (this.isDragging && e.touches.length === 1) { 269 if (this.isZoomed) { 270 // Pan 271 e.preventDefault(); 272 const deltaX = e.touches[0].clientX - this.startX; 273 const deltaY = e.touches[0].clientY - this.startY; 274 this.pointX = this.lastPointX + deltaX; 275 this.pointY = this.lastPointY + deltaY; 276 this.applyTransform(); 277 } else { 278 // Prevent default only if moving significantly to stop scrolling page (though overflow hidden handles most) 279 // But here we might want to swipe 280 } 281 } 282 } 283 284 handleTouchEnd(e) { 285 this.isZooming = false; 286 287 if (this.isDragging && !this.isZoomed) { 288 // Check for swipe 289 const diffX = e.changedTouches[0].clientX - this.startX; 290 if (Math.abs(diffX) > 50) { 291 if (diffX > 0) this.prev(); 292 else this.next(); 293 } 294 } 295 296 this.isDragging = false; 297 } 298 299 handleMouseDown(e) { 300 if (!this.isZoomed) return; 301 this.isDragging = true; 302 this.startX = e.clientX; 303 this.startY = e.clientY; 304 this.lastPointX = this.pointX; 305 this.lastPointY = this.pointY; 306 this.currentWrapper.style.cursor = "grabbing"; 307 e.preventDefault(); 308 } 309 310 handleMouseMove(e) { 311 if (this.isDragging && this.isZoomed) { 312 const deltaX = e.clientX - this.startX; 313 const deltaY = e.clientY - this.startY; 314 this.pointX = this.lastPointX + deltaX; 315 this.pointY = this.lastPointY + deltaY; 316 this.applyTransform(); 317 e.preventDefault(); 318 } 319 } 320 321 handleMouseUp(e) { 322 this.isDragging = false;
323 if (this.currentWrapper) this.currentWrapper.style.cursor = "grab"; 324 } 325 326 toggleZoom(e) { 327 if (this.isZoomed) { 328 this.scale = 1; 329 this.pointX = 0; 330 this.pointY = 0; 331 this.isZoomed = false; 332 this.currentWrapper.classList.remove("zoomed"); 333 } else { 334 this.scale = 2.5; 335 this.isZoomed = true; 336 this.currentWrapper.classList.add("zoomed"); 337 } 338 this.applyTransform(); 339 } 340 341 updateZoom(newScale) { 342 this.scale = Math.max(1, Math.min(newScale, 5)); 343 if (this.scale < 1.1) { 344 this.scale = 1; 345 this.isZoomed = false; 346 this.pointX = 0; 347 this.pointY = 0; 348 if (this.currentWrapper) this.currentWrapper.classList.remove("zoomed"); 349 } else { 350 this.isZoomed = true; 351 if (this.currentWrapper) this.currentWrapper.classList.add("zoomed"); 352 } 353 this.applyTransform(); 354 } 355 356 applyTransform() { 357 if (this.currentImage) { 358 this.currentImage.style.transform = `translate(${this.pointX}px, ${this.pointY}px) scale(${this.scale})`; 359 } 360 } 361} 362 363class PxCustomCarousel { 364 constructor(element, options = {}) { 365 this.element = element; 366 this.options = { 367 autoplay: false, 368 autoplayDelay: 4000, 369 enableTouch: true, 370 enableKeyboard: true, 371 enableNavigation: true, 372 loadingClass: "loading", 373 minQueueItems: 10, 374 ...options, 375 }; 376 377 const sourceItems = Array.from( 378 this.element.querySelectorAll(".px_carousel_item") 379 ); 380 this.baseItems = sourceItems.map((el, index) => { 381 const img = el.querySelector("img"); 382 return { 383 index, 384 href: el.getAttribute("href") || "#", 385 target: el.getAttribute("target") || "_blank", 386 thumb: img?.getAttribute("data-lazy") || img?.getAttribute("src") || "", 387 placeholder: img?.getAttribute("src") || "", 388 alt: img?.getAttribute("alt") || "", 389 }; 390 }); 391 this.baseTotalItems = this.baseItems.length; 392 this.queueStartIndex = 0; 393 this.renderCount = 0; 394 this.renderItems = []; 395 this.loopEnabled = this.baseTotalItems > 1; 396 this.suppressClickUntil = 0; 397 this.pendingSteps = 0; 398 399 // Parse items for gallery 400 this.galleryItems = this.baseItems.map((item) => ({ 401 src: item.href, 402 thumb: item.thumb, 403 alt: item.alt, 404 })); 405 406 // Initialize one valid global gallery instance 407 if (!window.pxGalleryInstance && this.galleryItems.length > 0) { 408 window.pxGalleryInstance = new PxGallery(this.galleryItems); 409 } else if (window.pxGalleryInstance) { 410 // Only update items if we found new ones (to support multiple carousels scenario loosely) 411 if (this.galleryItems.length > 0) { 412 window.pxGalleryInstance.items = this.galleryItems; 413 window.pxGalleryInstance.renderThumbnails(); 414 } 415 } 416 this.isAnimating = false; 417 this.transitionFallbackTimer = null; 418 this.resizeTimer = null; 419 this.transitionCallback = null; 420 this.autoplayTimer = null; 421 this.touchStartX = 0; 422 this.touchCurrentX = 0; 423 this.isDragging = false; 424 this.hasMoved = false; 425 this.startTime = null; 426 427 this.init(); 428 } 429 430 init() { 431 if (this.baseTotalItems === 0) return; 432 433 this.setupCarousel(); 434 this.setupNavigation(); 435 this.setupEvents(); 436 this.buildQueue(); 437 438 if (this.options.autoplay) { 439 this.startAutoplay(); 440 } 441 } 442 443 setupCarousel() { 444 this.container = this.element.querySelector(".px_carousel_container"); 445 this.element.classList.toggle("px_loop_enabled", this.loopEnabled); 446 if (this.options.enableTouch) { 447 this.element.classList.add("touch_enabled"); 448 } 449 } 450 451 setupNavigation() { 452 if (!this.options.enableNavigation) return; 453 454 this.prevBtn = document.createElement("button"); 455 this.prevBtn.className = "px_carousel_nav prev"; 456 this.prevBtn.innerHTML = '<i class="mdi mdi-chevron-left"></i>'; 457 this.prevBtn.setAttribute("aria-label", "Anterior"); 458 459 this.nextBtn = document.createElement("button"); 460 this.nextBtn.className = "px_carousel_nav next"; 461 this.nextBtn.innerHTML = '<i class="mdi mdi-chevron-right"></i>'; 462 this.nextBtn.setAttribute("aria-label", "Siguiente"); 463 464 this.element.appendChild(this.prevBtn); 465 this.element.appendChild(this.nextBtn); 466 467 this.prevBtn.addEventListener("click", (e) => { 468 e.stopPropagation(); 469 this.prev(); 470 }); 471 this.nextBtn.addEventListener("click", (e) => { 472 e.stopPropagation(); 473 this.next(); 474 }); 475 476 if (!this.loopEnabled) { 477 this.prevBtn.style.display = "none"; 478 this.nextBtn.style.display = "none"; 479 } 480 } 481 482 setupEvents() { 483 this.element.addEventListener("click", (e) =>
483 this.handleItemClick(e)); 484 485 if (this.options.enableTouch) { 486 this.element.addEventListener( 487 "touchstart", 488 (e) => this.handleTouchStart(e), 489 { passive: true } 490 ); 491 this.element.addEventListener( 492 "touchmove", 493 (e) => this.handleTouchMove(e), 494 { passive: false } 495 ); 496 this.element.addEventListener("touchend", (e) => this.handleTouchEnd(e), { 497 passive: true, 498 }); 499 } 500 501 this.element.addEventListener("mousedown", (e) => this.handleMouseDown(e)); 502 this.element.addEventListener("mousemove", (e) => this.handleMouseMove(e)); 503 this.element.addEventListener("mouseup", (e) => this.handleMouseUp(e)); 504 this.element.addEventListener("mouseleave", (e) => this.handleMouseUp(e)); 505 506 this.element.addEventListener("mouseenter", () => this.pauseAutoplay()); 507 this.element.addEventListener("mouseleave", () => this.resumeAutoplay()); 508 509 window.addEventListener("resize", () => this.handleResize()); 510 511 this.container.addEventListener("transitionend", (e) => { 512 if (e.target === this.container && e.propertyName === "transform") { 513 this.handleTransitionEnd(); 514 } 515 }); 516 } 517 518 next() { 519 if (!this.loopEnabled) return; 520 if (this.isAnimating) { 521 this.pendingSteps++; 522 return; 523 } 524 525 const item = this.createItem(this.queueStartIndex + this.renderCount); 526 this.container.appendChild(item); 527 this.renderItems.push(item); 528 this.animateTo(-this.getItemStep(), () => { 529 this.renderItems.shift()?.remove(); 530 this.queueStartIndex = this.normalizeBaseIndex(this.queueStartIndex + 1); 531 this.renderItems = Array.from(this.container.querySelectorAll(".px_carousel_item")); 532 this.updateSlidePosition(false); 533 this.processPendingSteps(); 534 }); 535 } 536 537 prev() { 538 if (!this.loopEnabled) return; 539 if (this.isAnimating) { 540 this.pendingSteps--; 541 return; 542 } 543 544 const item = this.createItem(this.queueStartIndex - 1); 545 this.container.insertBefore(item, this.container.firstChild); 546 this.renderItems.unshift(item); 547 this.updateSlidePosition(false, -this.getItemStep()); 548 this.animateTo(0, () => { 549 this.renderItems.pop()?.remove(); 550 this.queueStartIndex = this.normalizeBaseIndex(this.queueStartIndex - 1); 551 this.renderItems = Array.from(this.container.querySelectorAll(".px_carousel_item")); 552 this.updateSlidePosition(false); 553 this.processPendingSteps(); 554 }); 555 } 556 557 handleTransitionEnd() { 558 if (!this.loopEnabled) return; 559 560 this.clearTransitionFallback(); 561 const callback = this.transitionCallback; 562 this.transitionCallback = null; 563 this.isAnimating = false; 564 if (callback) callback(); 565 } 566 567 handleTouchStart(e) { 568 if (!this.loopEnabled) return; 569 if (this.isAnimating) return; 570 this.touchStartX = e.touches[0].clientX; 571 this.startTime = Date.now(); 572 this.hasMoved = false; 573 this.isDragging = false; 574 this.pauseAutoplay(); 575 } 576 577 handleTouchMove(e) { 578 if (!this.loopEnabled || !this.touchStartX) return; 579 this.touchCurrentX = e.touches[0].clientX; 580 const diff = Math.abs(this.touchStartX - this.touchCurrentX); 581 if (diff > 10 && !this.isDragging) { 582 this.isDragging = true; 583 this.hasMoved = true; 584 this.element.classList.add("dragging"); 585 e.preventDefault(); 586 } 587 if (this.isDragging) { 588 e.preventDefault(); 589 this.container.style.transform = `translateX(${-(this.touchStartX - this.touchCurrentX)}px)`; 590 } 591 } 592 593 handleTouchEnd(e) { 594 if (!this.loopEnabled) return; 595 const wasDragging = this.isDragging; 596 this.isDragging = false; 597 this.element.classList.remove("dragging"); 598 599 if (wasDragging && this.hasMoved) { 600 const diff = this.touchStartX - this.touchCurrentX; 601 if (Math.abs(diff) > 50) { 602 if (diff > 0) this.next(); 603 else this.prev(); 604 } else { 605 this.animateTo(0); 606 } 607 this.suppressClickUntil = Date.now() + 250; 608 } 609 this.touchStartX = 0; 610 this.touchCurrentX = 0; 611 this.hasMoved = false; 612 this.resumeAutoplay(); 613 } 614 615 handleMouseDown(e) { 616 if (!this.loopEnabled) return; 617 if (this.isAnimating) return; 618 this.touchStartX = e.clientX; 619 this.startTime = Date.now(); 620 this.hasMoved = false;
621 this.isDragging = false; 622 this.pauseAutoplay(); 623 } 624 625 handleMouseMove(e) { 626 if (!this.loopEnabled || !this.touchStartX) return; 627 this.touchCurrentX = e.clientX; 628 const diff = Math.abs(this.touchStartX - this.touchCurrentX); 629 if (diff > 5 && !this.isDragging) { 630 this.isDragging = true; 631 this.hasMoved = true; 632 this.element.classList.add("dragging"); 633 } 634 if (this.isDragging) { 635 this.container.style.transform = `translateX(${-(this.touchStartX - this.touchCurrentX)}px)`; 636 } 637 } 638 639 handleMouseUp(e) { 640 if (!this.loopEnabled) return; 641 const wasDragging = this.isDragging; 642 this.isDragging = false; 643 this.element.classList.remove("dragging"); 644 645 if (wasDragging && this.hasMoved) { 646 const diff = this.touchStartX - this.touchCurrentX; 647 if (Math.abs(diff) > 50) { 648 if (diff > 0) this.next(); 649 else this.prev(); 650 } else { 651 this.animateTo(0); 652 } 653 this.suppressClickUntil = Date.now() + 250; 654 } 655 this.touchStartX = 0; 656 this.touchCurrentX = 0; 657 this.hasMoved = false; 658 this.resumeAutoplay(); 659 } 660 661 handleResize() { 662 if (!this.loopEnabled) return; 663 window.clearTimeout(this.resizeTimer); 664 this.resizeTimer = window.setTimeout(() => { 665 this.clearTransitionFallback(); 666 this.isAnimating = false; 667 this.transitionCallback = null; 668 this.pendingSteps = 0; 669 this.buildQueue(); 670 }, 120); 671 } 672 673 startAutoplay() { 674 if (!this.loopEnabled) return; 675 this.autoplayTimer = setInterval(() => { 676 this.next(); 677 }, this.options.autoplayDelay); 678 } 679 680 pauseAutoplay() { 681 if (this.autoplayTimer) { 682 clearInterval(this.autoplayTimer); 683 this.autoplayTimer = null; 684 } 685 } 686 687 resumeAutoplay() { 688 if (this.options.autoplay && this.loopEnabled) { 689 this.startAutoplay(); 690 } 691 } 692 693 handleItemClick(e) { 694 const item = e.target.closest(".px_carousel_item"); 695 if (!item || !this.element.contains(item)) return; 696 697 e.preventDefault(); 698 699 if (Date.now() < this.suppressClickUntil) { 700 return; 701 } 702 703 const index = this.getBaseIndexFromItem(item); 704 if (window.pxGalleryInstance) { 705 window.pxGalleryInstance.open(index); 706 } 707 } 708 709 getBaseIndexFromItem(item) { 710 if (!item) return 0; 711 return this.normalizeBaseIndex(parseInt(item.dataset.baseIndex || "0", 10)); 712 } 713 714 normalizeBaseIndex(index) { 715 if (this.baseTotalItems === 0) return 0; 716 return ((index % this.baseTotalItems) + this.baseTotalItems) % this.baseTotalItems; 717 } 718 719 buildQueue() { 720 if (!this.container) return; 721 722 this.renderCount = this.getRenderCount(); 723 this.container.innerHTML = ""; 724 725 for (let i = 0; i < this.renderCount; i++) { 726 this.container.appendChild(this.createItem(this.queueStartIndex + i)); 727 } 728 729 this.renderItems = Array.from(this.container.querySelectorAll(".px_carousel_item")); 730 this.updateSlidePosition(false); 731 } 732 733 createItem(index) { 734 const baseIndex = this.normalizeBaseIndex(index); 735 const item = this.baseItems[baseIndex]; 736 const link = document.createElement("a"); 737 const img = document.createElement("img"); 738 739 link.className = "px_carousel_item"; 740 link.href = item.href; 741 link.target = item.target; 742 link.dataset.baseIndex = baseIndex; 743 744 img.src = item.thumb || item.placeholder; 745 img.alt = item.alt; 746 img.onerror = function () { 747 this.style.display = "none"; 748 }; 749 750 link.appendChild(img); 751 return link; 752 } 753 754 getRenderCount() { 755 if (!this.loopEnabled) return this.baseTotalItems; 756 757 const itemStep = this.getItemStep(); 758 const containerWidth = this.element.clientWidth || this.container.clientWidth; 759 const minQueueItems = Math.max(this.baseTotalItems, this.options.minQueueItems); 760 761 if (!itemStep || !containerWidth) { 762 return minQueueItems; 763 } 764 765 return Math.max(minQueueItems, Math.ceil(containerWidth / itemStep) + 2); 766 } 767 768 getItemStep() { 769 const firstItem = this.container.querySelector(".px_carousel_item") || this.element.querySelector(".px_carousel_item"); 770 const width = firstItem?.getBoundingClientRect().width || this.getFallbackItemWidth(); 771 return width + this.getGapSize(); 772 } 773 774 getFallbackItemWidth() { 775 if (window.matchMedia("(max-width: 480px)").matches) return 140; 776 if (window.matchMedia("(max-width: 768px)").matches) return 160; 777 return 260; 778 } 779 780 getGapSize() { 781 const styles = window.getComputedStyle(this.container); 782 return parseFloat(styles.columnGap || styles.gap || 0) || 0; 783 } 784 785 updateSlidePosition(animate = true, position = 0) { 786 this.container.style.transition = animate ? "" : "none"; 787 this.container.style.transform = `translateX(${position}px)`; 788 789 if (!animate) { 790 this.container.offsetHeight; 791 this.container.style.transition = ""; 792 } 793 } 794 795 animateTo(position, callback = null) { 796 this.isAnimating = true; 797 this.transitionCallback = callback; 798 this.updateSlidePosition(true, position); 799 this.queueTransitionFallback(); 800 } 801 802 processPendingSteps() { 803 if (this.pendingSteps > 0) { 804 this.pendingSteps--; 805 this.next(); 806 } else if (this.pendingSteps < 0) { 807 this.pendingSteps++; 808 this.prev(); 809 } 810 } 811 812 queueTransitionFallback() { 813 this.clearTransitionFallback(); 814 this.transitionFallbackTimer = window.setTimeout(() => { 815 this.handleTransitionEnd(); 816 }, 450); 817 } 818 819 clearTransitionFallback() { 820 if (this.transitionFallbackTimer) { 821 window.clearTimeout(this.transitionFallbackTimer); 822 this.transitionFallbackTimer = null; 823 } 824 } 825} 826 827document.addEventListener("DOMContentLoaded", function () { 828 const carousels = document.querySelectorAll(".px_custom_carousel");
829 carousels.forEach((carousel) => { 830 new PxCustomCarousel(carousel); 831 }); 832}); 833 834// Export manual 835window.PxCustomCarousel = PxCustomCarousel; 836window.PxGallery = PxGallery;
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.