→ לכל המאגר

G31 · גלריה אנכית נגררת עם מונה

GSAP GSAP · Observer הירו ופתיחהתמונות וגלריות ממתין

ערימת מדיה אנכית: הפריט המרכזי גדול, השכנים מציצים מעליו ומתחתיו. גרירה, החלקה או גלגלת מעבירים פריט, המונה מתעדכן, והכותרת מפוצלת לשני צידי הערימה.

מתי משתמשים: הירו של אתרי פורטפוליו ואולפנים, תצוגת פרויקטים נבחרים. הגלגלת עוברת פריטים רק בתוך הגלריה, ובקצוות היא משחררת את הדף (אין מלכודת גלילה).

הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.

הדמו

עבודות נבחרות
מהשנה האחרונה
1
2
3
4
5
6
7
01 / 07
גרור למעלה ולמטה, או גלגל
מובייל: החלקה אנכית בתוך הגלריה מחליפה פריט; touch-action מוגדר כך שהחלקה אופקית ממשיכה לגלול את הדף.

קוד להדבקה

הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --accent או --line משלו יורש אותם אוטומטית.

פתח קובץ עצמאי ↗
הצג את הקוד כאן

CSS

/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.stage{padding:var(--sec, clamp(120px, 12vw, 260px)) var(--gutter, clamp(24px, 5vw, 96px));position:relative}
.stage.tight{padding-block:clamp(48px,5vw,90px)}
.stage.full{padding-inline:0}
.ph{display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;border-radius:var(--r, 14px)}
.ph-a{background:linear-gradient(160deg,#3b5bdb,#748ffc)}
.ph-b{background:linear-gradient(160deg,#5f3dc4,#9775fa)}
.ph-c{background:linear-gradient(160deg,#0b7285,#3bc9db)}
.ph-d{background:linear-gradient(160deg,#2b8a3e,#69db7c)}
.ph-e{background:linear-gradient(160deg,#e8590c,#ffa94d)}
.ph-f{background:linear-gradient(160deg,#c92a2a,#ff8787)}
.ph-l{background:rgba(16,18,43,.72);color:#fff;padding:.18em .7em;border-radius:999px;line-height:1.3;font-size:max(.78em,12px)}

/* G31 · גלריה אנכית נגררת עם מונה */
html,body{overflow-x:clip}
.vg{position:relative;height:min(82vh,720px);background:#0f1020;color:#fff;overflow:hidden;user-select:none;touch-action:pan-x}
.vg-side{position:absolute;top:50%;transform:translateY(-50%);font-size:clamp(22px,2.4vw,40px);font-weight:700;white-space:nowrap;pointer-events:none}
.vg-side.r{right:var(--gutter, clamp(24px, 5vw, 96px))}.vg-side.l{left:var(--gutter, clamp(24px, 5vw, 96px))}
.vg-stack{position:absolute;inset:0;display:grid;place-items:center;cursor:grab}
.vg-stack:active{cursor:grabbing}
.vg-item{position:absolute;width:min(360px,40vw);aspect-ratio:4/3;font-size:28px;will-change:transform;box-shadow:0 20px 50px rgba(0,0,0,.35)}
.vg-count{position:absolute;bottom:22px;right:var(--gutter, clamp(24px, 5vw, 96px));font-variant-numeric:tabular-nums;font-size:14px;letter-spacing:.08em;color:#c9c9dd}
.vg-hint{position:absolute;bottom:22px;left:var(--gutter, clamp(24px, 5vw, 96px));font-size:13px;color:#8d8fa8}
@media(max-width:767px){.vg-side{display:none}.vg-item{width:64vw}}

HTML

<div class="stage full" style="padding-block:0"><div class="vg">
  <div class="vg-side r">עבודות נבחרות</div>
  <div class="vg-side l">מהשנה האחרונה</div>
  <div class="vg-stack">
    <div class="vg-item ph ph-a"><span class="ph-l">1</span></div><div class="vg-item ph ph-b"><span class="ph-l">2</span></div><div class="vg-item ph ph-c"><span class="ph-l">3</span></div>
    <div class="vg-item ph ph-d"><span class="ph-l">4</span></div><div class="vg-item ph ph-e"><span class="ph-l">5</span></div><div class="vg-item ph ph-f"><span class="ph-l">6</span></div><div class="vg-item ph ph-a"><span class="ph-l">7</span></div>
  </div>
  <div class="vg-count">01 / 07</div>
  <div class="vg-hint">גרור למעלה ולמטה, או גלגל</div>
</div></div>

JS

gsap.registerPlugin(Observer);

(function(){
  const vg=document.querySelector(".vg"),stack=vg.querySelector(".vg-stack"),items=[...vg.querySelectorAll(".vg-item")],count=vg.querySelector(".vg-count");
  const n=items.length;let cur=0,dragY=0,busy=false;
  const GAP=()=>items[0].offsetHeight*0.78;
  const pad=v=>String(v).padStart(2,"0");
  function layout(extra,instant){
    const g=GAP();
    items.forEach((it,i)=>{
      const off=i-cur;
      const y=off*g+(extra||0);
      const near=Math.abs(off+ (extra||0)/g);
      const props={y:y,scale:1.28-Math.min(near,1)*0.28,autoAlpha:Math.abs(off)>2?0:1,zIndex:20-Math.abs(off)};
      if(instant)gsap.set(it,props);else gsap.to(it,Object.assign(props,{duration:.8,ease:"expo.out",overwrite:true}));
    });
  }
  function go(step){
    const next=Math.max(0,Math.min(n-1,cur+step));
    if(next===cur){layout(0);return false;}
    cur=next;layout(0);
    count.textContent=pad(cur+1)+" / "+pad(n);
    gsap.fromTo(count,{y:6,autoAlpha:0},{y:0,autoAlpha:1,duration:.35,ease:"power2.out"});
    return true;
  }
  layout(0,true);
  Observer.create({target:stack,type:"touch,pointer",preventDefault:true,dragMinimum:3,
    onDragStart:()=>{dragY=0;},
    onDrag:self=>{dragY+=self.deltaY;layout(dragY,true);},
    onDragEnd:()=>{const steps=Math.round(-dragY/GAP());dragY=0;go(steps)||layout(0);}
  });
  vg.addEventListener("wheel",e=>{
    const dir=e.deltaY>0?1:-1;
    const canMove=(dir>0&&cur<n-1)||(dir<0&&cur>0);
    if(!canMove)return;
    e.preventDefault();
    if(busy)return;busy=true;go(dir);setTimeout(()=>busy=false,520);
  },{passive:false});
  addEventListener("resize",()=>layout(0,true));
})();