Slajder produktów — 4Hand

Dwa moduły Divi obok siebie:
Galeria z klasą sp-zrodlo — to jest źródło zdjęć
Kod z załączonym niżej kodem — to jest sam slajder
Galeria zostaje ukryta, slajder buduje się z niej sam.

Jak dołożyć produkt

  1. Otwórz moduł GaleriaAktualizuj galerię → zaznacz zdjęcia w bibliotece mediów.
  2. Podpis na pionowym pasku bierze się z pola Tytuł zdjęcia w bibliotece mediów — wpisz tam nazwę modelu, np. classic-012.
  3. Kolejność ustawiasz przeciąganiem w oknie wyboru zdjęć.
  4. W ustawieniach Galerii podnieś liczbę zdjęć do tylu, ile ich masz, i wyłącz stronicowanie — inaczej Divi pokaże tylko pierwszą porcję.

Co robi

  • Przesuwa się sam o jeden kafel, w lewo, w zapętleniu bez przeskoku.
  • Po najechaniu myszą przesuw staje, kafel rośnie w dół (górna krawędź zostaje w miejscu) i pojawia się żółty kwadrat z plusem.
  • Po kliknięciu otwiera się powiększenie — strzałki na klawiaturze i Esc działają.
  • Bez żadnej biblioteki zewnętrznej. Ten sam mechanizm co karuzela realizacji na stronie głównej.

Ustawienia

Wszystko siedzi w zmiennych na .sp-karuzela — zmieniasz na górze kodu, bez szukania w środku.

  • --sp-na-ekranie — ile kafli widać naraz (10 na komputerze, 5 na tablecie, 3 na telefonie)
  • --sp-odstep — przerwa między kaflami
  • --sp-proporcja — kształt kafla
  • --sp-przerwa — co ile milisekund następny krok
  • --sp-przejscie — jak długo trwa jeden krok
  • --sp-powiekszenie — o ile rośnie kafel po najechaniu

Uwaga na nazwy plików

Wgrywaj zdjęcia z nazwami bez polskich znaków. Nazwa z ogonkiem zapisuje się w dwóch różnych postaciach Unicode i część adresów przestaje działać.

Żywy podgląd

Galeria niżej jest ukryta — widać z niej wyłącznie slajder.

Kod do skopiowania

Całość wklejasz w jeden moduł Kod, razem ze znacznikami.

<style>
.sp-zrodlo{ display:none !important; }

.sp-karuzela{
  --sp-na-ekranie: 10;      /* ile kafli widac naraz */
  --sp-odstep: 30px;        /* przerwa miedzy kaflami */
  --sp-proporcja: 128/215;  /* ksztalt kafla */
  --sp-przerwa: 3000;       /* co ile ms nastepny krok */
  --sp-przejscie: 1500;     /* jak dlugo trwa krok */
  --sp-powiekszenie: 1.05;  /* o ile rosnie kafel po najechaniu */
  --sp-zolty: #F0C609;
  position:relative;
}

/* padding-bottom robi miejsce na kafel, ktory po najechaniu rozrasta sie
   w dol - przycinanie dziala dopiero na krawedzi wypelnienia, wiec
   powiekszony kafel i jego cien pozostaja widoczne */
.sp-karuzela .sp-viewport{ overflow:hidden; padding-bottom:26px; }
.sp-karuzela .sp-track{ display:flex; gap:var(--sp-odstep); will-change:transform; }

.sp-karuzela .sp-kafel{
  flex:0 0 calc((100% - (var(--sp-na-ekranie) - 1) * var(--sp-odstep)) / var(--sp-na-ekranie));
  position:relative; display:block; overflow:hidden; text-decoration:none; background:#000;
  transform-origin: top center;      /* gora stoi w miejscu, kafel rosnie w dol */
  transition: transform .5s cubic-bezier(.2,0,.2,1), box-shadow .5s ease;
}
.sp-karuzela .sp-kafel:hover{
  transform: scale(var(--sp-powiekszenie));
  z-index:5;
  box-shadow:0 0 20px 0 rgba(0,0,0,.35);
}

.sp-karuzela .sp-foto{ display:block; position:relative; overflow:hidden; aspect-ratio:var(--sp-proporcja); }
.sp-karuzela .sp-foto img{ display:block; width:100%; height:100%; object-fit:cover; }

/* nakladka: zolty kwadrat + dwie kreski jadace od krawedzi = plus
   ten sam mechanizm co w module hover-plus */
.sp-karuzela .sp-foto::after{
  content:""; position:absolute; inset:0; z-index:3; opacity:0;
  background-image:
    linear-gradient(#fff,#fff),
    linear-gradient(#fff,#fff),
    linear-gradient(var(--sp-zolty),var(--sp-zolty));
  background-repeat:no-repeat;
  background-size:3px 26px, 26px 3px, 56px 56px;
  background-position:50% 0%, 0% 50%, 50% 50%;
  transition:opacity .25s ease, background-position .5s cubic-bezier(.2,0,.2,1);
  pointer-events:none;
}
.sp-karuzela .sp-kafel:hover .sp-foto::after{
  opacity:1;
  background-position:50% 50%, 50% 50%, 50% 50%;
}

/* pionowy pasek z nazwa modelu przy prawej krawedzi kafla */
.sp-karuzela .sp-etykieta{
  position:absolute; top:0; right:0; bottom:0; width:24px; z-index:4;
  writing-mode:vertical-rl; background:rgba(0,0,0,.5); color:#fff;
  font-size:13px; font-weight:300; letter-spacing:3px; text-transform:uppercase;
  display:flex; align-items:center; justify-content:center;
  padding:5px 0; overflow:hidden; pointer-events:none;
}

.sp-karuzela .sp-arrows{ display:flex; gap:48px; justify-content:center; margin-top:10px; }
.sp-karuzela .sp-arrows button{
  background:none; border:0; cursor:pointer; color:var(--sp-zolty);
  font:600 14px/1 Poppins,system-ui,Arial,sans-serif; letter-spacing:2px;
  display:inline-flex; align-items:center; gap:10px; padding:8px;
}
.sp-karuzela .sp-arrows button:hover{ color:#323232; }
.sp-karuzela .sp-prev::before{ content:"\2190"; font-size:19px; }
.sp-karuzela .sp-next::after{ content:"\2192"; font-size:19px; }

@media (max-width:990px){ .sp-karuzela{ --sp-na-ekranie:5; } }
@media (max-width:590px){ .sp-karuzela{ --sp-na-ekranie:3; --sp-odstep:15px; } }

/* powiekszenie zdjecia */
.sp-lightbox{
  position:fixed; inset:0; z-index:99999; background:rgba(0,0,0,.92);
  display:flex; align-items:center; justify-content:center;
  opacity:0; visibility:hidden; transition:opacity .25s ease, visibility .25s;
}
.sp-lightbox.jest-otwarty{ opacity:1; visibility:visible; }
.sp-lightbox img{ max-width:88vw; max-height:84vh; display:block; }
.sp-lb-podpis{
  position:absolute; left:0; right:0; bottom:22px; text-align:center; color:#fff;
  letter-spacing:3px; text-transform:uppercase; font-size:13px;
}
.sp-lb-zamknij,.sp-lb-prev,.sp-lb-next{
  position:absolute; background:none; border:0; color:#fff; cursor:pointer;
  font-size:34px; line-height:1; padding:14px;
}
.sp-lb-zamknij:hover,.sp-lb-prev:hover,.sp-lb-next:hover{ color:#F0C609; }
.sp-lb-zamknij{ top:10px; right:14px; }
.sp-lb-prev{ left:10px; top:50%; transform:translateY(-50%); }
.sp-lb-next{ right:10px; top:50%; transform:translateY(-50%); }
</style>

<div class="sp-karuzela">
  <div class="sp-viewport"><div class="sp-track"></div></div>
  <div class="sp-arrows">
    <button class="sp-prev" type="button">PREV</button>
    <button class="sp-next" type="button">NEXT</button>
  </div>
</div>

<script>
(function(){
  function start(){
    document.querySelectorAll('.sp-karuzela').forEach(function(root){
      if(root.dataset.init) return; root.dataset.init='1';

      var track = root.querySelector('.sp-track');
      var viewport = root.querySelector('.sp-viewport');
      var styl = getComputedStyle(root);
      var PRZERWA   = parseFloat(styl.getPropertyValue('--sp-przerwa'))   || 3000;
      var PRZEJSCIE = parseFloat(styl.getPropertyValue('--sp-przejscie')) || 1500;

      /* ---------- kafle budowane z Galerii Divi ---------- */
      var zrodlo = document.querySelector(root.dataset.zrodlo || '.sp-zrodlo');
      if(zrodlo){
        [].forEach.call(zrodlo.querySelectorAll('.et_pb_gallery_item'), function(poz){
          var a = poz.querySelector('a');
          var img = poz.querySelector('img');
          if(!a || !img) return;
          var tytul = a.getAttribute('title') || img.getAttribute('alt') || '';

          var kafel = document.createElement('a');
          kafel.className = 'sp-kafel';
          kafel.setAttribute('href', a.getAttribute('href'));
          kafel.setAttribute('title', tytul);
          kafel.dataset.tytul = tytul;

          var foto = document.createElement('span');
          foto.className = 'sp-foto';
          var kopia = document.createElement('img');
          kopia.setAttribute('src', img.getAttribute('src'));
          if(img.getAttribute('srcset')) kopia.setAttribute('srcset', img.getAttribute('srcset'));
          kopia.setAttribute('alt', tytul);
          kopia.setAttribute('loading', 'lazy');
          foto.appendChild(kopia);

          var etykieta = document.createElement('span');
          etykieta.className = 'sp-etykieta';
          etykieta.textContent = tytul;

          kafel.appendChild(foto);
          kafel.appendChild(etykieta);
          track.appendChild(kafel);
        });
      }
      if(!track.children.length) return;   /* nie ma z czego budowac */

   /* numerujemy dopiero teraz - kafle moga pochodzic z galerii albo z kodu */
      [].forEach.call(track.children, function(a, i){ a.dataset.i = i; });

   /* lista do powiekszenia zbierana PRZED klonowaniem */
      var lista = [].map.call(track.children, function(a){
        return { src: a.getAttribute('href'), tytul: a.dataset.tytul || '' };
      });
      var oryg = track.children.length;
      track.innerHTML = track.innerHTML + track.innerHTML;   /* klon -> plynne zapetlenie */

      var offset = 0, tween = null, ostatni = null;
      var doNastepnego = PRZERWA, najechane = false, lbOtwarty = false;
      var bezRuchu = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

      function krok(){   /* szerokosc kafla + odstep */
        var a = track.children[0].getBoundingClientRect();
        var b = track.children[1].getBoundingClientRect();
        return b.left - a.left;
      }
      function jednostka(){ return krok() * oryg; }
      function ease(p){ return 1 - Math.pow(1 - p, 3); }
      function przesun(kierunek, czas){
        var od = tween ? tween.to : offset;
        tween = { from: offset, to: od + kierunek * krok(), start: performance.now(), dur: czas };
      }

      var prev = root.querySelector('.sp-prev'), next = root.querySelector('.sp-next');
      if(prev) prev.addEventListener('click', function(){ przesun(-1, 450); doNastepnego = PRZERWA; });
      if(next) next.addEventListener('click', function(){ przesun( 1, 450); doNastepnego = PRZERWA; });

      viewport.addEventListener('mouseenter', function(){ najechane = true; });
      viewport.addEventListener('mouseleave', function(){ najechane = false; });

      function klatka(t){
        if(ostatni === null) ostatni = t;
        var dt = t - ostatni; ostatni = t;

        if(tween){
          var p = (t - tween.start) / tween.dur;
          if(p >= 1){ offset = tween.to; tween = null; doNastepnego = PRZERWA; }
          else { offset = tween.from + (tween.to - tween.from) * ease(p); }
        } else if(!najechane && !lbOtwarty && !bezRuchu){
          doNastepnego -= dt;
          if(doNastepnego <= 0) przesun(1, PRZEJSCIE);
        }

        var u = jednostka();
        if(u > 0) offset = ((offset % u) + u) % u;
        track.style.transform = 'translateX(' + (-offset) + 'px)';
        requestAnimationFrame(klatka);
      }
      requestAnimationFrame(klatka);

      /* ---------- powiekszenie zdjecia ---------- */
      var lb = document.createElement('div');
      lb.className = 'sp-lightbox';
      lb.innerHTML = '<button class="sp-lb-zamknij" type="button" aria-label="Zamknij">&times;</button>'
                   + '<button class="sp-lb-prev" type="button" aria-label="Poprzednie">&#8249;</button>'
                   + '<img alt=""><span class="sp-lb-podpis"></span>'
                   + '<button class="sp-lb-next" type="button" aria-label="Nastepne">&#8250;</button>';
      document.body.appendChild(lb);
      var lbImg = lb.querySelector('img'), lbPodpis = lb.querySelector('.sp-lb-podpis');
      var biezacy = 0;

      function pokaz(i){
        biezacy = ((i % lista.length) + lista.length) % lista.length;
        lbImg.src = lista[biezacy].src;
        lbImg.alt = lista[biezacy].tytul;
        lbPodpis.textContent = lista[biezacy].tytul;
      }
      function otworz(i){ pokaz(i); lb.classList.add('jest-otwarty'); lbOtwarty = true; }
      function zamknij(){ lb.classList.remove('jest-otwarty'); lbOtwarty = false; }

      track.addEventListener('click', function(e){
        var a = e.target.closest('.sp-kafel');
        if(!a) return;
        e.preventDefault();
        otworz(parseInt(a.dataset.i, 10));
      });

      lb.querySelector('.sp-lb-zamknij').addEventListener('click', zamknij);
      lb.querySelector('.sp-lb-prev').addEventListener('click', function(e){ e.stopPropagation(); pokaz(biezacy - 1); });
      lb.querySelector('.sp-lb-next').addEventListener('click', function(e){ e.stopPropagation(); pokaz(biezacy + 1); });
      lb.addEventListener('click', function(e){ if(e.target === lb || e.target === lbImg) zamknij(); });
      document.addEventListener('keydown', function(e){
        if(!lbOtwarty) return;
        if(e.key === 'Escape')     zamknij();
        if(e.key === 'ArrowLeft')  pokaz(biezacy - 1);
        if(e.key === 'ArrowRight') pokaz(biezacy + 1);
      });
    });
  }
  if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();
</script>