Buy me a coffee

Lightbox Slideshow Swiper

Description

  • button with arrow hover effect
  • click on button will open autoscroll slideshow in lightbox
  • view demo – password: abc

01.26c16v10

01.26c16v10

#1. Install Code

#1.1. First use this code to Code Injection > Header

<!-- Lightbox Slideshow Swiper -->
<script src="https://cdn.jsdelivr.net/gh/willmyerscode/toolkit@1/index.min.js"></script>
<link href="https://cdn.jsdelivr.net/gh/willmyerscode/section-loader@3/section-loader.min.css" rel="stylesheet">

01.26c16v10

Next, use this code to Code Injection > Footer

<!-- Lightbox Slideshow Swiper -->
<script src="https://cdn.jsdelivr.net/gh/willmyerscode/section-loader@3/section-loader.min.js"></script>

01.26c16v10

Next, hover on page where you will use Button > Click Gear icon

01.26c16v10

Click Advanced > Paste this code

<!-- Lightbox Slideshow Swiper -->
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
<script>
!function(){"use strict";const e={targetSelector:'section[id*="lightbox-slideshow-swiper"] a[href="#lightbox-slideshow-swiper"].sqs-block-button-element',arrowSvg:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="currentColor"><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>'};function t(){const t=document.querySelector(e.targetSelector);if(!t)return;const n=t.textContent.trim(),o=t.getAttribute("href")||"#lightbox-slideshow-swiper",i=document.createElement("button");i.className="discover-btn",i.id="discoverBtn",i.setAttribute("data-href",o);const r=document.createElement("div");r.className="arrow-icon",r.innerHTML=e.arrowSvg;const s=document.createElement("span");s.className="text",s.textContent=n,i.appendChild(r),i.appendChild(s),i.addEventListener("click",(function(e){e.preventDefault();const t=this.getAttribute("data-href");if(t.startsWith("#")){const e=document.querySelector(t);e&&e.scrollIntoView({behavior:"smooth"})}else window.location.href=t})),t.parentNode.replaceChild(i,t)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",t):t(),window.Squarespace&&window.Squarespace.onInitialize&&window.Squarespace.onInitialize(Y,(function(){t()})),window.addEventListener("mercury:load",t)}();
</script>
<style>
  :root {
  --bullet-size: 8px;
  --bullet-active-size: 10px;
  --bullet-gap: 4px;
  --circle-border: 1.5px;
  --close-circle-size: 44px;
  --close-icon-size: 14px;
  --lightbox-slideshow-image-round-corners: 8px;
  --lightbox-slideshow-arrow-circle-size: 40px;
  --lightbox-slideshow-arrow-icon-size: 14px;
}
</style>
<script>
const tpSlideshowConfig = {
  sectionPageMap: {
    'section[id="lightbox-slideshow-swiper01"]': '/lightbox-slideshow-swiper'
  },
  autoplayEnabled: true,
  autoplayDelay: 3000
};

class TPSlideshowManager {
  constructor(config) {
    this.config = config;
    this.swiper = null;
    this.container = null;
    this.loadedSections = new Map();
    this.isOverLeft = false;
    this.isOverRight = false;
    this.init();
  }

  init() {
    this.createContainer();
    this.bindButtons();
  }

  createContainer() {
    this.container = document.createElement('div');
    this.container.className = 'tp-slideshow-container';
    this.container.innerHTML = `
      <button class="tp-close-btn">
        <svg viewBox="0 0 40 40">
          <path d="M4.3,35.7L35.7,4.3" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round"></path>
          <path d="M4.3,4.3l31.4,31.4" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round"></path>
        </svg>
      </button>
      <div class="tp-slideshow-content">
        <div class="tp-swiper swiper">
          <div class="swiper-wrapper"></div>
          <div class="swiper-pagination"></div>
        </div>
      </div>
      <div class="tp-custom-cursor">
        <svg class="caret-right-icon--small" viewBox="0 0 9 16">
          <polyline fill="none" stroke-miterlimit="10" points="1.6,1.2 6.5,7.9 1.6,14.7"></polyline>
        </svg>
        <svg class="caret-left-icon--small" viewBox="0 0 9 16">
          <polyline fill="none" stroke-miterlimit="10" points="7.3,14.7 2.5,8 7.3,1.2"></polyline>
        </svg>
      </div>
    `;
    document.body.appendChild(this.container);

    this.container.querySelector('.tp-close-btn').addEventListener('click', () => {
      this.close();
    });
  }

  bindButtons() {
    Object.keys(this.config.sectionPageMap).forEach(selector => {
      const section = document.querySelector(selector);
      if (section) {
        const button = section.querySelector('.button-block button');
        if (button) {
          button.addEventListener('click', async (e) => {
            e.preventDefault();
            const pageUrl = this.config.sectionPageMap[selector];
            await this.loadAndShow(pageUrl);
          });
        }
      }
    });
  }

  async loadAndShow(pageUrl) {
    try {
      let sectionContent;
      
      if (this.loadedSections.has(pageUrl)) {
        sectionContent = this.loadedSections.get(pageUrl);
      } else {
        sectionContent = await wm$.getFragment(pageUrl);
        this.loadedSections.set(pageUrl, sectionContent);
      }

      const images = this.extractImages(sectionContent);
      
      if (images.length > 0) {
        this.renderSwiper(images);
        this.open();
      }
    } catch (error) {
      console.error('Error loading section:', error);
    }
  }

  extractImages(sectionContent) {
    const images = [];
    const galleryItems = sectionContent.querySelectorAll('.gallery-grid-item img');
    
    galleryItems.forEach(img => {
      const src = img.getAttribute('data-src') || img.getAttribute('src');
      const alt = img.getAttribute('alt') || '';
      if (src) {
        images.push({ src, alt });
      }
    });
    
    return images;
  }

  renderSwiper(images) {
    const wrapper = this.container.querySelector('.swiper-wrapper');
    wrapper.innerHTML = images.map(img => `
      <div class="swiper-slide">
        <img src="${img.src}" alt="${img.alt}" loading="lazy">
      </div>
    `).join('');

    if (this.swiper) {
      this.swiper.destroy(true, true);
    }

    document.documentElement.style.setProperty('--autoplay-delay', `${this.config.autoplayDelay}ms`);

    const swiperConfig = {
      slidesPerView: 1,
      centeredSlides: true,
      spaceBetween: 30,
      loop: true,
      speed: 600,
      pagination: {
        el: '.tp-swiper .swiper-pagination',
        clickable: true,
        renderBullet: function (index, className) {
          return `<span class="${className}"></span>`;
        }
      },
      breakpoints: {
        768: {
          slidesPerView: 2
        },
        1024: {
          slidesPerView: 2
        }
      },
      on: {
        slideChange: () => {
          this.restartPaginationAnimation();
        }
      }
    };

    if (this.config.autoplayEnabled) {
      swiperConfig.autoplay = {
        delay: this.config.autoplayDelay,
        disableOnInteraction: false
      };
    }

    this.swiper = new Swiper('.tp-swiper', swiperConfig);
    this.initCustomCursor();
  }

  restartPaginationAnimation() {
    const activeBullet = this.container.querySelector('.swiper-pagination-bullet-active');
    if (activeBullet) {
      activeBullet.style.animation = 'none';
      activeBullet.offsetHeight;
      activeBullet.style.animation = '';
    }
  }

  initCustomCursor() {
    const customCursor = this.container.querySelector('.tp-custom-cursor');
    const carousel = this.container.querySelector('.tp-swiper');
    if (!customCursor || !carousel) return;

    const handleCarouselMouseMove = (e) => {
      const rect = carousel.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const width = rect.width;
      const prevSlide = carousel.querySelector('.swiper-slide-prev');
      const nextSlide = carousel.querySelector('.swiper-slide-next');

      if (x < width * 0.25 && prevSlide) {
        if (!this.isOverLeft) {
          this.isOverLeft = true;
          this.isOverRight = false;
          customCursor.classList.add('active', 'prev');
          document.body.style.cursor = 'none';
        }
      } else if (x > width * 0.75 && nextSlide) {
        if (!this.isOverRight) {
          this.isOverRight = true;
          this.isOverLeft = false;
          customCursor.classList.add('active');
          customCursor.classList.remove('prev');
          document.body.style.cursor = 'none';
        }
      } else {
        if (this.isOverLeft || this.isOverRight) {
          this.isOverLeft = false;
          this.isOverRight = false;
          customCursor.classList.remove('active', 'prev');
          document.body.style.cursor = 'default';
        }
      }
    };

    const handleCarouselMouseLeave = () => {
      this.isOverLeft = false;
      this.isOverRight = false;
      customCursor.classList.remove('active', 'prev');
      document.body.style.cursor = 'default';
    };

    const handleCarouselClick = (e) => {
      const rect = carousel.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const width = rect.width;

      if (x < width * 0.25) {
        this.swiper?.slidePrev();
      } else if (x > width * 0.75) {
        this.swiper?.slideNext();
      }
    };

    const handleDocumentMouseMove = (e) => {
      if (customCursor.classList.contains('active')) {
        customCursor.style.left = e.clientX + 'px';
        customCursor.style.top = e.clientY + 'px';
      }
    };

    carousel.addEventListener('mousemove', handleCarouselMouseMove);
    carousel.addEventListener('mouseleave', handleCarouselMouseLeave);
    carousel.addEventListener('click', handleCarouselClick);
    document.addEventListener('mousemove', handleDocumentMouseMove);
  }

  open() {
    this.container.classList.add('active');
    document.body.style.overflow = 'hidden';
  }

  close() {
    this.container.classList.remove('active');
    document.body.style.overflow = '';
    if (this.swiper) {
      this.swiper.autoplay?.stop();
    }
  }
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => {
    new TPSlideshowManager(tpSlideshowConfig);
  });
} else {
  new TPSlideshowManager(tpSlideshowConfig);
}
</script>
<style>
section[id*="lightbox-slideshow-swiper"] .button-block{background:var(--secondaryButtonBackgroundColor)}section[id*="lightbox-slideshow-swiper"] .button-block svg{width:15px;height:15px}.discover-btn{position:relative;z-index:1;background:#fff0;border:none;color:#fff;cursor:pointer;display:flex;align-items:center;gap:20px;padding:10px}.discover-btn .text{font-size:14px;letter-spacing:3px;text-transform:uppercase;font-weight:300}.discover-btn .arrow-icon{width:50px;height:50px;border:1px solid rgb(255 255 255 / .5);border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;transition:border-color 200ms ease}.discover-btn .arrow-icon i{font-size:16px}.discover-btn .arrow-icon::after{content:'';position:absolute;top:calc(var(--circle-border) * -1);left:calc(var(--circle-border) * -1);width:calc(100% + var(--circle-border) * 2);height:calc(100% + var(--circle-border) * 2);border-radius:50%;background:conic-gradient(white var(--hover-progress,0deg),transparent 0deg);mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));opacity:0;--hover-progress:0deg}.discover-btn:hover .arrow-icon{border-color:#fff0}.discover-btn:hover .arrow-icon::after{opacity:1;animation:drawCircleHover 500ms linear forwards}@property --hover-progress{syntax:'<angle>';initial-value:0deg;inherits:false}@keyframes drawCircleHover{0%{--hover-progress:0deg}100%{--hover-progress:360deg}}
</style>
<style>
.tp-slideshow-container{position:fixed;bottom:0;left:0;width:100%;height:100vh;background:#fff;z-index:9999;transform:translateY(100%);transition:transform 0.6s cubic-bezier(.4,0,.2,1);overflow:hidden}.tp-slideshow-container.active{transform:translateY(0)}.tp-close-btn{position:fixed;top:30px;left:50%;transform:translateX(-50%);width:var(--close-circle-size);height:var(--close-circle-size);background:#fff0;border:1px solid rgb(0 0 0 / .5);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:border-color 0.2s ease;z-index:10000}.tp-close-btn::after{content:'';position:absolute;top:calc(var(--circle-border) * -1);left:calc(var(--circle-border) * -1);width:calc(100% + var(--circle-border) * 2);height:calc(100% + var(--circle-border) * 2);border-radius:50%;background:conic-gradient(#000 var(--close-progress,0deg),transparent 0deg);mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));opacity:0;--close-progress:0deg}.tp-close-btn:hover{border-color:#fff0}.tp-close-btn:hover::after{opacity:1;animation:drawCircleClose 500ms linear forwards}.tp-close-btn svg{width:var(--close-icon-size);height:var(--close-icon-size);transition:transform 0.3s ease;z-index:1}.tp-close-btn:hover svg{transform:rotate(90deg)}@property --close-progress{syntax:'<angle>';initial-value:0deg;inherits:false}@keyframes drawCircleClose{0%{--close-progress:0deg}100%{--close-progress:360deg}}.tp-slideshow-content{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20px}.tp-slideshow-content .gallery-grid{display:none}.tp-swiper{width:100%;max-width:1400px;height:60vh;min-height:400px;max-height:600px;margin-bottom:20px;position:relative;overflow:visible}@media (max-width:768px){.tp-swiper{height:45vh;min-height:300px}}.tp-swiper .swiper-slide{display:flex;align-items:center;justify-content:center;overflow:hidden;border-radius:var(--lightbox-slideshow-image-round-corners);position:relative;transition:transform 0.5s cubic-bezier(.4,0,.2,1),opacity 0.5s cubic-bezier(.4,0,.2,1);opacity:.5;transform:scale(.75)!important}.tp-swiper .swiper-slide-active{opacity:1;transform:scale(1)!important;z-index:2}.tp-swiper .swiper-slide-prev,.tp-swiper .swiper-slide-next{opacity:.7;transform:scale(.8)!important}.tp-swiper .swiper-slide img{width:100%;height:100%;object-fit:cover}.tp-swiper .swiper-slide-prev,.tp-swiper .swiper-slide-next{cursor:none}.tp-swiper .swiper-slide-active{cursor:default}.tp-swiper .swiper-pagination{position:relative;bottom:auto;width:100%;margin-top:30px;display:flex;justify-content:center;align-items:center;gap:var(--bullet-size);z-index:10}.tp-swiper .swiper-pagination-bullet{width:var(--bullet-size);height:var(--bullet-size);background:#000;opacity:.4;border-radius:50%;cursor:pointer;transition:all 0.3s ease}.tp-swiper .swiper-pagination-bullet-active{width:var(--bullet-active-size);height:var(--bullet-active-size);background:#000;opacity:1;position:relative}@property --progress{syntax:'<angle>';initial-value:0deg;inherits:false}.tp-swiper .swiper-pagination-bullet-active::after{content:'';position:absolute;top:calc(var(--bullet-gap) * -1);left:calc(var(--bullet-gap) * -1);width:calc(var(--bullet-active-size) + var(--bullet-gap) * 2);height:calc(var(--bullet-active-size) + var(--bullet-gap) * 2);border-radius:50%;background:conic-gradient(#000 var(--progress),transparent 0deg);mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--circle-border)),#000 calc(100% - var(--circle-border)));--progress:0deg;animation:drawCircle var(--autoplay-delay,5s) linear forwards}@keyframes drawCircle{0%{--progress:0deg}100%{--progress:360deg}}.tp-custom-cursor{position:fixed;width:var(--lightbox-slideshow-arrow-circle-size);height:var(--lightbox-slideshow-arrow-circle-size);border:2px solid #fff;border-radius:50%;pointer-events:none;z-index:9999;display:none;align-items:center;justify-content:center;transform:translate(-50%,-50%);transition:opacity 0.2s ease}.tp-custom-cursor svg{width:var(--lightbox-slideshow-arrow-icon-size);height:var(--lightbox-slideshow-arrow-icon-size);stroke:#fff;stroke-width:2}.tp-custom-cursor .caret-left-icon--small{display:none}.tp-custom-cursor.active{display:flex}.tp-custom-cursor.prev .caret-right-icon--small{display:none}.tp-custom-cursor.prev .caret-left-icon--small{display:block}
</style>

01.26c16v10

#1.2. Next, create a Page in Not Linked with URL Slug: /lightbox-slideshow-swiper

01.26c16v10

Edit this Page > Add a Gallery Section (choose Images > Choose section with (i) icon)

01.26c16v10

Make sure you choose Grid Simple

01.26c16v10

Next, add Images to Gallery

01.26c16v10

#1.3. Next, edit page in #1.1 (page we will add button) and add a Button Block

01.26c16v10

Add URL: #lightbox-slideshow-wrapper to Button URL

and disable Open in New Tab

01.26c16v10

#1.4. Next, hover on top right of section > Click Edit Section

01.26c16v10

at Anchor Link, enter word: lightbox-slideshow-swiper01

lightbox-slideshow-swiper01

01.26c16v10

#2. Customize

You can see some options in Line 8 to Line 18

  :root {
  --bullet-size: 8px;
  --bullet-active-size: 10px;
  --bullet-gap: 4px;
  --circle-border: 1.5px;
  --close-circle-size: 44px;
  --close-icon-size: 14px;
  --lightbox-slideshow-image-round-corners: 8px;
  --lightbox-slideshow-arrow-circle-size: 40px;
  --lightbox-slideshow-arrow-icon-size: 14px;
}

01.26c16v10

#2.1. To disable autoscroll slideshow in lightbox, you can change this line to false (line 25)

01.26c16v10