גלריה עם פוקוס

גלריה שמרגישה כמו מצלמה ולא כמו קרוסלה: המבקר יודע בדיוק איזה פריט הוא "הזה". הטשטוש, הגודל וההטיה נגזרים מהמרחק מהמרכז, ולכן הגלילה עצמה מזיזה את הפוקוס. מתחת זה scroll-snap רגיל, כך שגרירה, מגע, גלגלת ומקלדת עובדים בלי קוד נוסף, גם בכיוון ימין לשמאל. גלריה עם פוקוס הוא רכיב מתוך רכיבים של כליקס. מה שמורידים הוא רכיב שלם, גם כ-HTML עצמאי וגם כרכיב React. אין ספרייה חיצונית, אין שלב בנייה, ואם כליקס ייעלם מחר הקוד ימשיך לעבוד. מתאים לעמודים ולמסמכים שעוסקים בגלריה, תיק עבודות, גלילה ובלי ספריות. באותה סדרה יש גם שאלות נפוצות, ציר תהליך וקרוסלת המלצות.

רכיב · גלריה · תיק עבודות · גלילה · בלי ספריות

<!-- גלריה עם פוקוס: כל כרטיס הוא figure עם klix-focus-card. רוחב כרטיס: --klix-card -->
<style>
.klix-focus{position:relative;width:100%}
.klix-focus-track{display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;
  padding:1.5rem calc(50% - var(--klix-card,15rem) / 2);perspective:1100px}
.klix-focus-track::-webkit-scrollbar{display:none}
.klix-focus-card{flex:0 0 var(--klix-card,15rem);margin:0;scroll-snap-align:center;border-radius:1.1rem;overflow:hidden;
  background:#1e293b;transform-origin:center;will-change:transform,filter}
.klix-focus-nav{display:flex;justify-content:center;gap:.5rem}
.klix-focus-nav button{width:2.5rem;height:2.5rem;border-radius:50%;border:1px solid #334155;background:#0f172a;
  color:#e2e8f0;font:inherit;cursor:pointer}
html[data-motion="off"] .klix-focus-card{transform:none!important;filter:none!important;opacity:1!important}
</style>

<div class="klix-focus" style="--klix-card:15rem">
  <div class="klix-focus-track">
    <figure class="klix-focus-card"><div style="height:10rem;background:#fbbf24;display:grid;place-items:center;font-size:2.4rem;font-weight:900;color:#0f172a">1</div>
      <figcaption style="padding:.8rem 1rem;font-weight:700">עיצוב חלל</figcaption></figure>
    <figure class="klix-focus-card"><div style="height:10rem;background:#34d399;display:grid;place-items:center;font-size:2.4rem;font-weight:900;color:#0f172a">2</div>
      <figcaption style="padding:.8rem 1rem;font-weight:700">מטבח בהתאמה</figcaption></figure>
    <figure class="klix-focus-card"><div style="height:10rem;background:#60a5fa;display:grid;place-items:center;font-size:2.4rem;font-weight:900;color:#0f172a">3</div>
      <figcaption style="padding:.8rem 1rem;font-weight:700">חדר שינה</figcaption></figure>
    <figure class="klix-focus-card"><div style="height:10rem;background:#f472b6;display:grid;place-items:center;font-size:2.4rem;font-weight:900;color:#0f172a">4</div>
      <figcaption style="padding:.8rem 1rem;font-weight:700">מרפסת</figcaption></figure>
    <figure class="klix-focus-card"><div style="height:10rem;background:#a78bfa;display:grid;place-items:center;font-size:2.4rem;font-weight:900;color:#0f172a">5</div>
      <figcaption style="padding:.8rem 1rem;font-weight:700">משרד ביתי</figcaption></figure>
  </div>
  <div class="klix-focus-nav">
    <button type="button" data-dir="-1" aria-label="הקודם">›</button>
    <button type="button" data-dir="1" aria-label="הבא">‹</button>
  </div>
</div>

<script>
(function () {
  function off() { return document.documentElement.getAttribute('data-motion') === 'off'; }

  document.querySelectorAll('.klix-focus').forEach(function (box) {
    var track = box.querySelector('.klix-focus-track');
    var cards = Array.prototype.slice.call(track.children);
    var current = 0, raf = 0;

    function update() {
      raf = 0;
      var r = track.getBoundingClientRect(), mid = r.left + r.width / 2, bestD = Infinity;
      cards.forEach(function (c, i) {
        var b = c.getBoundingClientRect();
        // המרחק מהמרכז ביחידות של רוחב כרטיס: ממנו נגזר הכול
        var d = (b.left + b.width / 2 - mid) / b.width, a = Math.min(Math.abs(d), 2);
        if (Math.abs(d) < bestD) { bestD = Math.abs(d); current = i; }
        if (off()) { c.style.transform = c.style.filter = c.style.opacity = ''; return; }
        c.style.transform = 'rotateY(' + (Math.max(-1, Math.min(1, d)) * -14) + 'deg) scale(' + (1 - a * 0.07) + ')';
        c.style.filter = a < 0.05 ? 'none' : 'blur(' + (a * 2.6).toFixed(2) + 'px)';
        c.style.opacity = String(1 - a * 0.22);
      });
      cards.forEach(function (c, i) {
        if (i === current) c.setAttribute('aria-current', 'true'); else c.removeAttribute('aria-current');
      });
    }

    function schedule() { if (!raf) raf = requestAnimationFrame(update); }

    box.querySelectorAll('.klix-focus-nav button').forEach(function (btn) {
      btn.addEventListener('click', function () {
        var i = Math.max(0, Math.min(cards.length - 1, current + Number(btn.getAttribute('data-dir'))));
        // scrollIntoView ולא scrollLeft: בימין-לשמאל הערך שונה בין דפדפנים
        cards[i].scrollIntoView({ behavior: off() ? 'auto' : 'smooth', inline: 'center', block: 'nearest' });
      });
    });

    track.addEventListener('scroll', schedule, { passive: true });
    window.addEventListener('resize', schedule);
    update();
  });
})();
</script>