רצועת כרטיסים שנעה לרוחב לפי הגלילה האנכית, וכל כרטיס גדל ומתחדד כשהוא מגיע למרכז המסך ומתכווץ כשהוא יוצא ממנו.
מתי משתמשים: תיק עבודות, קטלוג מוצרים, ציר שנים, המלצות. מפנה את תשומת הלב לפריט אחד בכל רגע במקום להציג שורה שטוחה.
הדמו כאן עיצובי-ניטרלי בכוונה. כשהמהלך נכנס לפרויקט, מיובאת רק ההתנהגות: הצבעים, הרדיוסים, הפונטים והצללים יורשים את העיצוב של אותו פרויקט.
גלול. מי שבמרכז גדל וחד, והשאר נסוגים.
הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --accent או --line משלו יורש אותם אוטומטית. הרכיב הזה מסתמך על dir="rtl".
/* מחלקות עזר של המאגר שהרכיב הזה משתמש בהן */
.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}
<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>
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();
})();