Jake Vanderwerf
5 days ago 75a097a018a0090f5902758353c578fce4aa2a25
assets/js/concise/Gallery.js
@@ -26,14 +26,14 @@
      this.initModal();
      this.initListeners();
      this.initSubscribers();
      console.log('Gallery loaded...');
   }
   /*********************************************************************
    ELEMENTS
    *********************************************************************/
   initElements() {
      this.elements = {
         imageSelector: 'a.open-gallery',
         imageSelector: 'img[data-gallery]',
         gallery: {
            modal: 'dialog.gallery',
            wrap: '.wrap',
@@ -56,25 +56,24 @@
            closeMessage: 'Closed Gallery',
         }
      );
      this.modal.subscribe((event, data) => {
      this.modal.subscribe((event) => {
         if (event === 'modal-close') {
            this.toggleGallery(false);
         }
      });
   }
   buildGalleryItems(filtered = null) {
      let selector = filtered ? `[data-opens="${filtered}"]` : this.elements.imageSelector;
      let selector = filtered ? `[data-gallery="${filtered}"]` : this.elements.imageSelector;
      this.items = Array.from(document.querySelectorAll(selector))
         .map((img, index) => {
            let image = img.querySelector('img');
            return {
               id: img.dataset.id||index,
               small: image.dataset.small || img.src,
               medium: image.dataset.medium || img.src,
               full: image.dataset.full || img.src,
               alt: image.alt || '',
               element: image
               srcset: img.srcset || img.src, // Clone the srcset from page
               sizes: img.sizes || '100vw',
               src: img.currentSrc || img.src, // Fallback
               full: img.dataset.full || img.src,
               alt: img.alt || '',
               element: img
            };
         });
   }
@@ -95,9 +94,10 @@
      let target = window.targetCheck(e, this.elements.imageSelector);
      if (target && !this.modal.isOpen) {
         e.preventDefault();
         this.buildGalleryItems((Object.hasOwn(target.dataset, 'opens')) ? target.dataset.opens : null);
         this.buildGalleryItems(target.dataset.gallery || null);
         this.index = this.items.findIndex(item => item.element === target.querySelector('img'));
         // Target is now the img element itself
         this.index = this.items.findIndex(item => item.element === target);
         this.toggleGallery(true);
      } else if (this.modal.isOpen) {
         if (window.targetCheck(e, this.elements.gallery.nextButton)) {
@@ -140,6 +140,9 @@
   }
   onPointerDown(e) {
      // Always prevent default to stop browser's native image drag
      e.preventDefault();
      this.swipe.startX = e.clientX;
      this.swipe.startY = e.clientY;
      this.ui.gallery.image.setPointerCapture(e.pointerId);
@@ -177,6 +180,8 @@
         this.zoom.panning = true;
         this.zoom.startX = e.clientX - this.zoom.x;
         this.zoom.startY = e.clientY - this.zoom.y;
         // Change cursor to grabbing
         this.ui.gallery.image.style.cursor = 'grabbing';
      }
   }
@@ -188,17 +193,12 @@
      // PINCH (two pointers)
      if (this.activePointers.size === 2) {
         const pts = [...this.activePointers.values()];
         const dist = Math.hypot(
            pts[0].x - pts[1].x,
            pts[0].y - pts[1].y
         );
         const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
         const increment = this.pinchStartScale * (dist / this.pinchStartDist) - this.zoom.scale;
         const newScale = this.pinchStartScale * (dist / this.pinchStartDist);
         const increment = newScale - this.zoom.scale;
         // Zoom centered (NO midpoint)
         this.handleZoom(increment);
         const midX = (pts[0].x + pts[1].x) / 2;  // ← anchor to finger midpoint
         const midY = (pts[0].y + pts[1].y) / 2;
         this.handleZoom(increment, midX, midY);
         return;
      }
@@ -211,30 +211,27 @@
   }
   onPointerUp(e) {
      const last = this.activePointers.get(e.pointerId); // grab before delete
      this.activePointers.delete(e.pointerId);
      if (this.activePointers.size < 2) {
         this.pinchStartDist = 0;
      }
      if (!this.zoom.panning && this.activePointers.size === 0) {
         // End of tap or swipe - detect swipe
         this.swipe.endX = e.clientX;
         this.swipe.endY = e.clientY;
         const dx = this.swipe.endX - this.swipe.startX;
         const dy = this.swipe.endY - this.swipe.startY;
      if (this.zoom.scale <= 1 && !this.zoom.panning && this.activePointers.size === 0) {
         const endX = last?.x ?? e.clientX; // use tracked position, fall back to event
         const endY = last?.y ?? e.clientY;
         const dx = endX - this.swipe.startX;
         const dy = endY - this.swipe.startY;
         if (Math.abs(dx) > this.zoom.threshold) {
            if (dx > 0) {
               console.log('Swipe right');
               this.prevElement();
            } else {
               console.log('Swipe left');
               this.nextElement();
            }
         if (Math.abs(dx) > this.zoom.threshold && Math.abs(dx) > Math.abs(dy)) {
            dx > 0 ? this.prevElement() : this.nextElement();
         }
      }
      if (this.activePointers.size === 0) {
         this.zoom.panning = false;
         this.ui.gallery.image.style.cursor = this.zoom.scale > 1 ? 'grab' : 'default';
      }
   }
@@ -249,45 +246,6 @@
      this.handleZoom(increment, e.clientX, e.clientY);
   }
   clampPan() {
      const BORDER = 32; // 2rem
      const wrap = this.ui.gallery.wrap;
      if (!wrap) return;
      const wrapRect = wrap.getBoundingClientRect();
      // MUST use natural dimensions
      const naturalW = 1920;
      const naturalH = 1920;
      // But they must be constrained by how the CSS fits them (max-width: 90vw, max-height: 85vh)
      // So compute the base display size:
      const displayRatio = Math.min(
         wrapRect.width / naturalW,
         wrapRect.height / naturalH
      );
      const baseWidth  = naturalW * displayRatio;
      const baseHeight = naturalH * displayRatio;
      const scaledWidth  = baseWidth  * this.zoom.scale;
      const scaledHeight = baseHeight * this.zoom.scale;
      // Allowed pan range
      const minX = wrapRect.width - scaledWidth - BORDER;
      const maxX = BORDER;
      const minY = wrapRect.height - scaledHeight - BORDER;
      const maxY = BORDER;
      // clamp
      this.zoom.x = Math.min(maxX, Math.max(minX, this.zoom.x));
      this.zoom.y = Math.min(maxY, Math.max(minY, this.zoom.y));
   }
   handleZoom(increment, clientX=null, clientY=null) {
      const oldScale = this.zoom.scale;
      let newScale = oldScale + increment;
@@ -318,9 +276,18 @@
   }
   applyTransform() {
      // this.clampPan();
      this.clampPan();
      const img = this.ui.gallery.image;
      img.style.transform = `translate(${this.zoom.x}px, ${this.zoom.y}px) scale(${this.zoom.scale})`;
      // Update cursor based on zoom level
      img.style.cursor = this.zoom.scale > 1 ? 'grab' : 'default';
   }
   clampPan() {
      const img = this.ui.gallery.image;
      const excessX = Math.max(0, (img.offsetWidth  * this.zoom.scale - window.innerWidth)  / 2);
      const excessY = Math.max(0, (img.offsetHeight * this.zoom.scale - window.innerHeight) / 2);
      this.zoom.x = Math.max(-excessX, Math.min(excessX, this.zoom.x));
      this.zoom.y = Math.max(-excessY, Math.min(excessY, this.zoom.y));
   }
   resetZoom() {
      this.zoom.scale = 1;
@@ -344,10 +311,13 @@
   /**
    *
    * @param {boolean} open
    * @param {null|number} index
    */
   toggleGallery(open, index= null) {
   toggleGallery(open) {
      if (open) {
         // Disable native image dragging
         this.ui.gallery.image.draggable = false;
         this.ui.gallery.image.style.userSelect = 'none';
         this.ui.gallery.image.addEventListener("pointerdown", this.pointerDownHandler);
         this.ui.gallery.image.addEventListener("pointermove", this.pointerMoveHandler);
         this.ui.gallery.image.addEventListener("pointerup", this.pointerUpHandler);
@@ -406,8 +376,30 @@
   updateDisplay() {
      const item = this.items[this.index];
      if (!item) return;
      this.ui.gallery.image.src = item.full;
      this.ui.gallery.image.alt = item.alt;
      const galleryImg = this.ui.gallery.image;
      // Set srcset first - browser uses cached version instantly (no wait)
      if (item.srcset) {
         galleryImg.srcset = item.srcset;
         galleryImg.sizes = item.sizes;
      }
      galleryImg.src = item.src; // Fallback
      galleryImg.alt = item.alt;
      // ALWAYS load full resolution for zoom quality
      if (item.full && item.full !== item.src) {
         const fullImg = new Image();
         fullImg.onload = () => {
            if (this.items[this.index] === item) {
               galleryImg.src = item.full;
               galleryImg.removeAttribute('srcset'); // Switch to full res directly
               galleryImg.removeAttribute('sizes');
            }
         };
         fullImg.src = item.full;
      }
      this.ui.gallery.counter.textContent = `${this.index + 1} / ${this.items.length}`;
      this.ui.gallery.prevButton.disabled = this.items.length <= 1;