modal-dialogsliderphotoswipeswiper.js

Combining Swiper slider and photoswipe


I'm looking for a combination of Swiper slider and Photoswipe (Or other lightbox).

Trying to make a product slider with 3 products in a slide.

Each product has a lightbox/modal with video and a gallery. The modals are generated within the boundaries of the product div. When you click an 'open gallery' / 'show video' link. The lightbox opens fullscreen.

The problem I'm having is: the lightbox won't (but has to) exceed the boundary of the slider product boundary.

Looking for a solution.

Something like an empty modal/lightbox containers outside the slider with dynamic content when an 'open modal' link is clicked within the product slide.


Solution

  • You can check it, here is example:

    <header>
      <h1>
        <a title="swiper.js" href="http://idangero.us/swiper/" target="_blank">Swiper.js (5.3.7)</a> &
        <a title="photoswipe" href="http://photoswipe.com/" target="_blank">Photoswipe.js (4.1.3)</a> - Mobile Native feel
        slider gallery
      </h1>
      <p>Combine two of the most powerfull JS plugins (Endless options / Great docs / Fast / Modern / Mobile freindly) -
        <a title="swiper.js" href="http://idangero.us/swiper/" target="_blank">SWIPER</a> IS PERFECT FOR THIS IDEA BEACUSE OF
        ITS unique <code>preventClicks</code> Parameter (Prevent accidental unwanted clicks on links during swiping) -
        <strong>Works like magic</strong>. Also its really <b>hard</b> to find - Code example of working photoswipe
        combination with any slider out there(slick, flickity, owl etc.) and
        in general slider & lightbox - so i hope this example be usefull for you.</p>
    </header>
    
    <!-- https://swiperjs.com/get-started/ -->
    <!-- Slider main container -->
    <div class="swiper-container">
      <!-- Additional required wrapper -->
      <ul class="swiper-wrapper my-gallery" itemscope itemtype="http://schema.org/ImageGallery">
        <!-- Slides -->
        <li id="1" class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
          <a id="first" title="click to zoom-in" href="https://picsum.photos/id/1079/1200/600" itemprop="contentUrl" data-size="1200x600">
            <img src="https://picsum.photos/id/1079/1200/600" itemprop="thumbnail" alt="Image description" />
          </a>
        </li>
        <li id="2" class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
          <a title="click to zoom-in" href="http://placehold.it/1200x601/AB47BC/ffffff?text=Zoom-image-2"
             itemprop="contentUrl" data-size="1200x601">
            <img src="http://placehold.it/600x300/AB47BC/ffffff?text=Thumbnail-image-2" itemprop="thumbnail" alt="Image description" />
          </a>
        </li>
        <li id="3" class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
          <a title="click to zoom-in" href="http://placehold.it/1200x600/EF5350/ffffff?text=Zoom-image-3" itemprop="contentUrl" data-size="1200x600">
            <img src="http://placehold.it/600x300/EF5350/ffffff?text=Thumbnail-image-3" itemprop="thumbnail" alt="Image description" />
          </a>
        </li>
        <li id="4" class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
          <a title="click to zoom-in" href="http://placehold.it/1200x600/1976D2/ffffff?text=Zoom-image-4" itemprop="contentUrl" data-size="1200x600">
            <img src="http://placehold.it/600x300/1976D2/ffffff?text=Thumbnail-image-4" itemprop="thumbnail" alt="Image description" />
          </a>
        </li>
        <li id="5" class="swiper-slide" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
          <a title="click to zoom-in" href="https://picsum.photos/id/1011/1200/600" itemprop="contentUrl"
             data-size="1200x600">
            <img src="https://picsum.photos/id/1011/1200/600" itemprop="thumbnail" alt="Image description" />
          </a>
        </li>
      </ul>
    
      <!-- Add Pagination -->
      <div class="swiper-pagination"></div>
    
      <!-- If we need navigation buttons -->
      <div title="Prev" class="swiper-button-prev"></div>
      <div title="Next" class="swiper-button-next"></div>
    </div>
    
    <!-- https://photoswipe.com/documentation/getting-started.html -->
    <!-- add PhotoSwipe (.pswp) element to DOM - 
        Root element of PhotoSwipe. Must have class pswp. -->
    <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
    
      <!-- Background of PhotoSwipe. 
              It's a separate element, as animating opacity is faster than rgba(). -->
      <div class="pswp__bg"></div>
    
      <!-- Slides wrapper with overflow:hidden. -->
      <div class="pswp__scroll-wrap">
    
        <!-- Container that holds slides. PhotoSwipe keeps only 3 slides in DOM to save memory. -->
        <!-- don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
          <div class="pswp__item"></div>
          <div class="pswp__item"></div>
          <div class="pswp__item"></div>
        </div>
    
        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">
    
          <div class="pswp__top-bar">
    
            <!--  Controls are self-explanatory. Order can be changed. -->
    
            <div class="pswp__counter"></div>
    
            <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
    
            <button class="pswp__button pswp__button--share" title="Share"></button>
    
            <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
    
            <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
    
            <!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->
            <!-- element will get class pswp__preloader--active when preloader is running -->
            <div class="pswp__preloader">
              <div class="pswp__preloader__icn">
                <div class="pswp__preloader__cut">
                  <div class="pswp__preloader__donut"></div>
                </div>
              </div>
            </div>
          </div>
    
          <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
            <div class="pswp__share-tooltip"></div>
          </div>
    
          <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
          </button>
    
          <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
          </button>
    
          <div class="pswp__caption">
            <div class="pswp__caption__center"></div>
          </div>
    
        </div>
    
      </div>
    
    </div>
    
    <!-- ////////////////////////
        DOCS
        ////////////////////////////
        -->
    
    <!-- Include Tippy -->
    <script src="https://unpkg.com/tippy.js@3/dist/tippy.all.min.js"></script>
    <!-- OPTIONAL: Set the defaults for the auto-initialized tooltips -->
    <script>
      tippy('.swiper-button-prev', { 
        content: "Prev",
        theme: "light",
        arrow: true,
      })
    
      tippy('.swiper-button-next', { 
        content: "Next",
        theme: "light",
        arrow: true,
      })
    </script>
    
    
    <section id="docs">
      <br>
      <br>
      <br>
      <hr>
      <h2>Noted / Important</h2>
    
      <ol>
        <li>
          <h3>
            A non-jQuery dependent
          </h3>
        </li>
        <li>
          <h3>Cdns</h3>
          <h4>Head (CSS)</h4>
          <code>
            &lt;!-- photoswipe CSS --&gt;
            <br>
            &lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.css&quot; /&gt;
            <br>
            &lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.min.css&quot; /&gt;
            <br>
            &lt;!-- swiper CSS --&gt;
            <br>
            &lt;link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/swiper/css/swiper.min.css&quot; /&gt;
          </code>
          <h4>Before body (JS)</h4>
          <code>
            &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js&quot;&gt;&lt;/script&gt;
            <br>
            &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js&quot;&gt;&lt;/script&gt;
            <br>
            &lt;script src=&quot;https://unpkg.com/swiper/js/swiper.min.js&quot;&gt;&lt;/script&gt;
            <br>
            &lt;!-- copy-paste the code under codepen js tab her (wrap with script tag) --&gt;
          </code>
        </li>
        <li>
          <h3>Loop & Counter</h3>
          <p>
            <strong> Wont work well </strong> with swiper: <code>loop = true;</code> & photoswipe: <code>counterEl:
            true,</code>(What is counter? example: 1/5...2/5) - "loop" duplicate images - the photoswipe counter will be
            wrong. *** If you dont want a
            loop - you can set photoswipe counter <code>counterEl: true,</code>
          </p>
        </li>
        <li>
          <h3><a target="_blank" href="https://schema.org/ImageGallery">Schema.org - ImageGallery</a> Markup (0 ERRORS 0 WARNINGS )</h3>
          <p>
            <a href="">Schema.org</a> markup + semantic HTML: use unordered (bulleted) list (If you want a <code>div</code> under photoswipe - change JS -
            <strong>"(find) control+f-->"</strong> tagname value) . Copy-paste - this code to check: <a target="_blank"
                                                                                                        href="https://search.google.com/structured-data/testing-tool">Structured Data Testing Tool - Google</a>
          </p>
        </li>
        <li>
          <h3>Match index - BY API</h3>
          <p>
            <strong>
              Extra CODE "match index"
            </strong> - EXAMPLE: When you click(zoom) image1 -- goes to image 2 - close image2 (X) - also the swiper update
            is position (<strong>BETTER</strong> User Experience) (find<kbd>(ctr +f)</kbd>-->
            <code>mySwiper.slideTo(getCurrentIndex, false);</code>) -
            This idea miss
            in most slider & lightbox examples/plugins mixed.
            <br>
            Very simple code idea (100% API solution) - get photoswipe index (for example 2) and swiper slideTo index (2 - in this example).
          <ul>
            <li >
              <a target="_blank" href="https://photoswipe.com/documentation/api.html">Photoswipe API - <strong>pswp.getCurrentIndex()</strong></a>
            <li style="border-top-width: 0;"> <a target="_blank" href="https://swiperjs.com/api/#methods">Swiper API - <strong>slideTo(index);</strong></a>
            </li>
    
            </li>
        </ul>
    
      </p>
    </li>
    <li>
      <h3>Photoswipe options</h3>
      <p>
        JS - line (find) -ctr +f --> the term:<code>// define options (if needed)</code>. You find endless options for
        <strong>photoswipe</strong> - This is the place to add/modify options. Full Options list her
        <a href="http://photoswipe.com/documentation/options.html" target="_blank">PhotoSwipe
          Options</a>
      </p>
    </li>
    
    <li>
      <h3>SWIPER options</h3>
      <h4>slideperview</h4>
      <p>
        <code>slideperview</code> - option1: Set number (1,2,3 and so on) <a
                                                                             href="http://idangero.us/swiper/demos/110-slides-per-view.html" target="_blank"> - example </a> |||||
        option2(<b>"Carousel Mode"</b> this example): Set to "<code>auto</code>"
        than add CSS <a href="https://www.w3schools.com/cssref/pr_dim_width.asp" target="_blank">width
        Property</a></code> <code>.swiper-slide</code> (in thie case eash slide is 88% width) - <a
                                                                                                   href="http://idangero.us/swiper/demos/120-slides-per-view-auto.html" target="_blank">example</a>.
    </p>
    <h4>spaceBetween & centeredSlides</h4>
    <p>
      Space Between slide by js option <code>spaceBetween</code> - and also usefull to change
      <code>centeredSlides</code>(true/flase). <br>
      <a href="http://idangero.us/swiper/api/" target="_blank">Swiper API</a>
    </p>
    </li>
    </ol>
    <hr>
    <h3>Related Example</h3>
    <p>
      <a title="FancyBox3 & Flickity" href="https://codepen.io/ezra_siton/pen/OQmjoq" target="_blank">#FancyBox3 -
        lightbox & Flickity Slider</a>
    </p>
    </section>
    
    /* zero custom styles for photoswipe */
    
    /*==================================
    SWIPER - minimal styling
    ===================================*/
    /* semantic HTML - remove bullet and space from the list */
    ul.swiper-wrapper {
      list-style-type: none;
      margin: 0;
      padding: 0;
    }
    
    /* Swiper container */
    .swiper-container {
      max-width: 100%;
    }
    
    /* swiper responive image */
    .swiper-container img {
      width: 100%;
      height: auto;
    }
    
    .swiper-slide {
      /* Remove this if you want 1 slide perview - than change slidesPerView js-option to 1 -or- 2+ instead of 'auto' */
      width: 80%;
    }
    
    /* Swiper custom pagination */
    .swiper-pagination-bullet {
      width: 34px;
      height: 34px;
      text-align: center;
      line-height: 34px;
      font-size: 14px;
      color: #000;
      opacity: 1;
      background: rgba(0, 0, 0, 0.3);
      transition: background-color 0.5s ease, color 0.5s ease;
    }
    
    /* Swiper custom pagination */
    .swiper-pagination-bullet:hover {
      transition: background-color 0.5s ease;
      background: rgba(0, 0, 0, 1);
      color: white;
    }
    
    /* Swiper custom pagination active state */
    .swiper-pagination-bullet-active {
      color: #fff;
      background: black;
    }
    
    
    /*==================================================================
    CODEPEN STYLES -(under codepen gear/setting icon)
    ==============================================++++++================*/
    .tippy-tooltip.light-theme {
      color: #26323d;
      box-shadow: 0 0 20px 4px rgba(154, 161, 177, 0.15),
        0 4px 80px -8px rgba(36, 40, 47, 0.25),
        0 4px 4px -2px rgba(91, 94, 105, 0.15);
      background-color: white;
    }
    
    .tippy-backdrop {
      background-color: white;
    }
    
    .tippy-roundarrow {
      fill: white;
    }
    
    /* Default (sharp) arrow */
    .tippy-popper[x-placement^='top'] .tippy-tooltip.light-theme .tippy-arrow {
      border-top-color: #fff;
    }
    .tippy-popper[x-placement^='bottom'] .tippy-tooltip.light-theme .tippy-arrow {
      border-bottom-color: #fff;
    }
    .tippy-popper[x-placement^='left'] .tippy-tooltip.light-theme .tippy-arrow {
      border-left-color: #fff;
    }
    .tippy-popper[x-placement^='right'] .tippy-tooltip.light-theme .tippy-arrow {
      border-right-color: #fff;
    }
    /* Round arrow */
    .tippy-tooltip.light-theme .tippy-roundarrow {
      fill: #fff;
    }
    
    /* TOC 
    part one - Swiper instilaze
    part two - photoswipe instilaze
    part three - photoswipe define options
    part four - extra code (update swiper index when image close and micro changes)
    
    /* 1 of 4 : SWIPER ################################### */
    var mySwiper = new Swiper(".swiper-container", {
      // If swiper loop is true set photoswipe counterEl: false (line 175 her)
      loop: true,
      /* slidesPerView || auto - if you want to set width by css like flickity.js layout - in this case width:80% by CSS */
      slidesPerView: "auto",
      spaceBetween: 10,
      centeredSlides: true,
      slideToClickedSlide: false,
      autoplay: { /* remove/comment to stop autoplay  */
        delay: 3000,
        disableOnInteraction: false /* true by deafult */
      },
      // If we need pagination
      pagination: {
        el: ".swiper-pagination",
        clickable: true,
        renderBullet: function(index, className) {
          return '<span class="' + className + '">' + (index + 1) + "</span>";
        }
      },
      // Navigation arrows
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
      // keyboard control
      keyboard: {
        enabled: true,
      }
    });
    
    // 2 of 4 : PHOTOSWIPE #######################################
    // https://photoswipe.com/documentation/getting-started.html //
    
    var initPhotoSwipeFromDOM = function(gallerySelector) {
      // parse slide data (url, title, size ...) from DOM elements
      // (children of gallerySelector)
      var parseThumbnailElements = function(el) {
        var thumbElements = el.childNodes,
            numNodes = thumbElements.length,
            items = [],
            figureEl,
            linkEl,
            size,
            item;
    
        for (var i = 0; i < numNodes; i++) {
          figureEl = thumbElements[i]; // <figure> element
    
          // include only element nodes
          if (figureEl.nodeType !== 1) {
            continue;
          }
    
          linkEl = figureEl.children[0]; // <a> element
    
          size = linkEl.getAttribute("data-size").split("x");
    
          // create slide object
          item = {
            src: linkEl.getAttribute("href"),
            w: parseInt(size[0], 10),
            h: parseInt(size[1], 10)
          };
    
          if (figureEl.children.length > 1) {
            // <figcaption> content
            item.title = figureEl.children[1].innerHTML;
          }
    
          if (linkEl.children.length > 0) {
            // <img> thumbnail element, retrieving thumbnail url
            item.msrc = linkEl.children[0].getAttribute("src");
          }
    
          item.el = figureEl; // save link to element for getThumbBoundsFn
          items.push(item);
        }
    
        return items;
      };
    
      // find nearest parent element
      var closest = function closest(el, fn) {
        return el && (fn(el) ? el : closest(el.parentNode, fn));
      };
    
      // triggers when user clicks on thumbnail
      var onThumbnailsClick = function(e) {
        e = e || window.event;
        e.preventDefault ? e.preventDefault() : (e.returnValue = false);
    
        var eTarget = e.target || e.srcElement;
    
        // find root element of slide
        var clickedListItem = closest(eTarget, function(el) {
          return el.tagName && el.tagName.toUpperCase() === "LI";
        });
    
        if (!clickedListItem) {
          return;
        }
    
        // find index of clicked item by looping through all child nodes
        // alternatively, you may define index via data- attribute
        var clickedGallery = clickedListItem.parentNode,
            childNodes = clickedListItem.parentNode.childNodes,
            numChildNodes = childNodes.length,
            nodeIndex = 0,
            index;
    
        for (var i = 0; i < numChildNodes; i++) {
          if (childNodes[i].nodeType !== 1) {
            continue;
          }
    
          if (childNodes[i] === clickedListItem) {
            index = nodeIndex;
            break;
          }
          nodeIndex++;
        }
    
        if (index >= 0) {
          // open PhotoSwipe if valid index found
          openPhotoSwipe(index, clickedGallery);
        }
        return false;
      };
    
      // parse picture index and gallery index from URL (#&pid=1&gid=2)
      var photoswipeParseHash = function() {
        var hash = window.location.hash.substring(1),
            params = {};
    
        if (hash.length < 5) {
          return params;
        }
    
        var vars = hash.split("&");
        for (var i = 0; i < vars.length; i++) {
          if (!vars[i]) {
            continue;
          }
          var pair = vars[i].split("=");
          if (pair.length < 2) {
            continue;
          }
          params[pair[0]] = pair[1];
        }
    
        if (params.gid) {
          params.gid = parseInt(params.gid, 10);
        }
    
        return params;
      };
    
      var openPhotoSwipe = function(
      index,
       galleryElement,
       disableAnimation,
       fromURL
      ) {
        var pswpElement = document.querySelectorAll(".pswp")[0],
            gallery,
            options,
            items;
    
        items = parseThumbnailElements(galleryElement);
    
        // #################### 3/4 define photoswipe options (if needed) #################### 
        // https://photoswipe.com/documentation/options.html //
        options = {
          /* "showHideOpacity" uncomment this If dimensions of your small thumbnail don't match dimensions of large image */
          //showHideOpacity:true,
    
          // Buttons/elements
          closeEl: true,
          captionEl: true,
          fullscreenEl: true,
          zoomEl: true,
          shareEl: false,
          counterEl: false,
          arrowEl: true,
          preloaderEl: true,
          // define gallery index (for URL)
          galleryUID: galleryElement.getAttribute("data-pswp-uid"),
          getThumbBoundsFn: function(index) {
            // See Options -> getThumbBoundsFn section of documentation for more info
            var thumbnail = items[index].el.getElementsByTagName("img")[0], // find thumbnail
                pageYScroll =
                window.pageYOffset || document.documentElement.scrollTop,
                rect = thumbnail.getBoundingClientRect();
    
            return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
          }
        };
    
        // PhotoSwipe opened from URL
        if (fromURL) {
          if (options.galleryPIDs) {
            // parse real index when custom PIDs are used
            // http://photoswipe.com/documentation/faq.html#custom-pid-in-url
            for (var j = 0; j < items.length; j++) {
              if (items[j].pid == index) {
                options.index = j;
                break;
              }
            }
          } else {
            // in URL indexes start from 1
            options.index = parseInt(index, 10) - 1;
          }
        } else {
          options.index = parseInt(index, 10);
        }
    
        // exit if index not found
        if (isNaN(options.index)) {
          return;
        }
    
        if (disableAnimation) {
          options.showAnimationDuration = 0;
        }
    
        // Pass data to PhotoSwipe and initialize it
        gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
        gallery.init();
    
        /* ########### PART 4 - EXTRA CODE  ########### */
        /* EXTRA CODE (NOT FROM photoswipe CORE) - 
        1/2. UPDATE SWIPER POSITION TO THE CURRENT ZOOM_IN IMAGE (BETTER UI) */
        // photoswipe event: Gallery unbinds events
        // (triggers before closing animation)
        gallery.listen("unbindEvents", function() {
          // This is index of current photoswipe slide
          var getCurrentIndex = gallery.getCurrentIndex();
          // Update position of the slider
          mySwiper.slideTo(getCurrentIndex, false);
          // 2/2. Start swiper autoplay (on close - if swiper autoplay is true)
          mySwiper.autoplay.start();
        });
        // 2/2. Extra Code (Not from photo) - swiper autoplay stop when image zoom */
        gallery.listen('initialZoomIn', function() {
          if(mySwiper.autoplay.running){
            mySwiper.autoplay.stop();
          }
        });
      };
    
      // loop through all gallery elements and bind events
      var galleryElements = document.querySelectorAll(gallerySelector);
    
      for (var i = 0, l = galleryElements.length; i < l; i++) {
        galleryElements[i].setAttribute("data-pswp-uid", i + 1);
        galleryElements[i].onclick = onThumbnailsClick;
      }
    
      // Parse URL and open gallery if it contains #&pid=3&gid=1
      var hashData = photoswipeParseHash();
      if (hashData.pid && hashData.gid) {
        openPhotoSwipe(hashData.pid, galleryElements[hashData.gid - 1], true, true);
      }
    };
    
    // execute above function
    initPhotoSwipeFromDOM(".my-gallery");
    
    

    https://codepen.io/ezra_siton/pen/XNpJaX/