→ לכל המאגר

G65 · רצועה אופקית שהפריט במרכזה גדל

GSAP GSAP · ScrollTrigger · containerAnimation תמונות וגלריותכרטיסיםתהליך ושלבים ממתין

רצועת כרטיסים שנעה לרוחב לפי הגלילה האנכית, וכל כרטיס גדל ומתחדד כשהוא מגיע למרכז המסך ומתכווץ כשהוא יוצא ממנו.

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

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

הדמו

משרד עורכי דין
מותג קוסמטיקה
קורס דיגיטלי
פורטל לקוחות
קליניקה פרטית
יבואן ריהוט

גלול. מי שבמרכז גדל וחד, והשאר נסוגים.

שני דברים. הראשון הוא מלכודת ה-RTL שכבר תפסה אותנו: ילד `width:max-content` בתוך קונטיינר RTL מתחיל מהקצה הימני, ואז כל המדידות של ScrollTrigger יוצאות מוזזות. לכן המעטפת מקבלת `direction:ltr` והרצועה עצמה `rtl`. השני הוא שיטת המדידה: במקום טריגר נפרד לכל כרטיס, יש לולאת ticker אחת שמודדת את המרחק מהמרכז ומתרגמת אותו לגודל, לאטימות ולחדות. **שני הידוקים שנדרשו**: המרחק נמדד ביחידות של רוחב כרטיס ולא של חצי מסך, אחרת גם השכן הצמוד כמעט זהה למרכזי ואין פריט אחד שמושך את העין; והלולאה נדלקת ונכבית עם ScrollTrigger, אחרת היא מודדת שישה אלמנטים בכל פריים לכל אורך העמוד.

קוד להדבקה

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

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

CSS

/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.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)}

/* G65 · רצועה אופקית שהפריט במרכזה גדל */
.ctr{height:340vh;position:relative}
.ctr-stage{position:sticky;top:0;height:100vh;overflow:hidden;display:grid;align-content:center}
/* בעמוד עברי הרצועה נשארת RTL, אבל המדידה של ScrollTrigger דורשת הקשר LTR על המעטפת */
.ctr-view{direction:ltr;overflow:hidden}
.ctr-track{display:flex;gap:clamp(16px,2.4vw,40px);width:max-content;padding-inline:44vw;direction:rtl}
.ctr-item{width:clamp(180px,22vw,320px);flex:none;border-radius:18px;overflow:hidden;background:var(--card, #fff);
  border:1px solid var(--line, #e4e4ee);will-change:transform,filter}
.ctr-item .ph{aspect-ratio:3/4;border-radius:0;font-size:0}
.ctr-item b{display:block;padding:12px 14px 14px;font-size:15px}
.ctr-cap{position:absolute;bottom:8vh;inset-inline:0;text-align:center;color:var(--muted, #6a6d85);font-size:15px}

HTML

<div class="ctr"><div class="ctr-stage">
  <div class="ctr-view"><div class="ctr-track">
    <article class="ctr-item"><div class="ph ph-a"></div><b>משרד עורכי דין</b></article>
    <article class="ctr-item"><div class="ph ph-c"></div><b>מותג קוסמטיקה</b></article>
    <article class="ctr-item"><div class="ph ph-d"></div><b>קורס דיגיטלי</b></article>
    <article class="ctr-item"><div class="ph ph-e"></div><b>פורטל לקוחות</b></article>
    <article class="ctr-item"><div class="ph ph-b"></div><b>קליניקה פרטית</b></article>
    <article class="ctr-item"><div class="ph ph-f"></div><b>יבואן ריהוט</b></article>
  </div></div>
  <p class="ctr-cap">גלול. מי שבמרכז גדל וחד, והשאר נסוגים.</p>
</div></div>

JS

gsap.registerPlugin(ScrollTrigger);

(function(){
  const track=document.querySelector(".ctr-track"),view=document.querySelector(".ctr-view");
  const items=gsap.utils.toArray(".ctr-item");
  const reduce=matchMedia("(prefers-reduced-motion: reduce)").matches;
  const dist=()=>track.scrollWidth-view.clientWidth;
  // בעברית הרצועה מתחילה בימין, ולכן היא נעה ל-x חיובי
  const move=gsap.fromTo(track,{x:-dist()},{x:0,ease:"none",
    scrollTrigger:{trigger:".ctr",start:"top top",end:"bottom bottom",scrub:.5,invalidateOnRefresh:true}});
  if(reduce)return;

  // הדעיכה נמדדת ביחידות של כרטיס אחד ולא של חצי מסך. עם חצי מסך גם השכן הצמוד
  // מקבל כמעט את אותו גודל, ואז אין באמת פריט אחד שתופס את תשומת הלב.
  const step=()=>{
    const first=items[0].getBoundingClientRect();
    const gap=items.length>1?Math.abs(items[1].getBoundingClientRect().left-first.left):first.width;
    return gap||first.width||1;
  };
  const paint=()=>{
    const mid=innerWidth/2,unit=step();
    items.forEach(el=>{
      const r=el.getBoundingClientRect();
      const d=Math.min(1,Math.abs(r.left+r.width/2-mid)/(unit*1.7)); // 0 במרכז, 1 בערך שני כרטיסים משם
      const f=1-d*d;                                             // דעיכה רכה בקצה, חדה במרכז
      gsap.set(el,{scale:.84+f*.24,opacity:.5+f*.5,
        filter:"blur("+((1-f)*2.4).toFixed(2)+"px)",zIndex:Math.round(f*10)});
    });
  };

  // הלולאה רצה רק כשהרצועה על המסך. בלי זה היא מודדת שישה אלמנטים בכל פריים
  // לכל אורך העמוד, גם כשאין מה לראות.
  let running=false;
  const startLoop=()=>{if(!running){running=true;gsap.ticker.add(paint)}};
  const stopLoop=()=>{if(running){running=false;gsap.ticker.remove(paint)}};
  ScrollTrigger.create({trigger:".ctr",start:"top bottom",end:"bottom top",
    onToggle:self=>self.isActive?startLoop():stopLoop()});
  paint();
})();