→ לכל המאגר

G47 · אלמנט שנוסע בין תחנות לאורך הגלילה

GSAP GSAP · Flip.fit · ScrollTrigger תהליך ושלביםתמונות וגלריותהירו ופתיחה ממתין

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

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

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

הדמו

גלול למטה, הדמו מגיע ↓

גלול. הפריט עובר בין המסגרות במקום להתחלף.

שלב 01

שיחת אפיון

מבינים את העסק, את הלקוח ואת מה שצריך לקרות באתר, לפני שנוגעים בעיצוב.

שלב 02

קופי ומבנה

כותבים את המסרים ובונים את סדר הסקשנים, כך שכל גלילה עונה על השאלה הבאה.

שלב 03

עיצוב ופיתוח

הופכים את המבנה לאתר חי, מהיר, שנראה נכון בכל מסך.

שלב 04

עלייה ומדידה

מחברים מעקב, עולים לאוויר, ובודקים מה עובד במקום לנחש.

עוד מסלול גלילה. נסה גם חזרה למעלה ↑
Flip.fit שונה מ-Flip.from: הוא לא מנפיש הפרש שכבר קרה אלא מצמיד אלמנט אחד לקופסה של אלמנט אחר, כולל גודל וזווית. המסגרות בעמוד נשארות שקופות ומשמשות רק כמדידה, ולכן העמוד רספונסיבי בלי חישובי פיקסלים. שתי מלכודות: ה-duration של כל קטע חייב להיות המרחק האמיתי בין התחנות (אחרת הנסיעה מאיצה ומאטה בלי סיבה), ובשינוי גודל חלון חייבים לבנות מחדש עם clearProps, כי ה-fit הישן שמור בטרנספורם. במצב חיסכון בתנועה מציגים את המסגרות עצמן ומוותרים על הנוסע.

קוד להדבקה

הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --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-l{background:rgba(16,18,43,.72);color:#fff;padding:.18em .7em;border-radius:999px;line-height:1.3;font-size:max(.78em,12px)}

/* G47 · אלמנט שנוסע בין תחנות לאורך הגלילה */
.jf-hint{text-align:center;color:var(--muted, #6a6d85);font-size:14px;padding-bottom:10px}
.jf-step{min-height:88vh;display:grid;grid-template-columns:1fr 1fr;align-items:center;gap:clamp(20px,4vw,60px);
  max-width:min(1080px,94vw);margin-inline:auto}
.jf-step:nth-child(even){direction:rtl}
.jf-copy h3{margin:0 0 10px;font-size:clamp(22px,2.8vw,40px)}
.jf-copy p{margin:0;color:var(--muted, #6a6d85);font-size:17px;line-height:1.7;max-width:42ch}
.jf-num{font-size:13px;color:var(--accent, #4a3aff);font-weight:700;letter-spacing:.08em;margin-bottom:8px}
.jf-zone{aspect-ratio:1;justify-self:center;width:min(78%,340px)}
.jf-step:nth-child(2) .jf-zone{width:min(52%,220px)}
.jf-step:nth-child(3) .jf-zone{width:min(88%,380px)}
.jf-step:nth-child(4) .jf-zone{width:min(46%,190px)}
/* הנוסע יושב מחוץ לזרימה אבל בתוך אותו קונטקסט גלילה של התחנות, ו-Flip.fit מציב אותו על התחנה הנוכחית */
.jf-wrap{position:relative}
.jf-trav{position:absolute;top:0;left:0;width:340px;aspect-ratio:1;border-radius:22px;
  z-index:2;pointer-events:none;display:grid;place-items:center;font-size:40px;box-shadow:0 24px 60px rgba(20,20,40,.22)}
@media(max-width:760px){.jf-step{grid-template-columns:1fr;gap:24px;min-height:auto;padding-block:14vh}
  .jf-zone,.jf-step:nth-child(2) .jf-zone,.jf-step:nth-child(3) .jf-zone,.jf-step:nth-child(4) .jf-zone{width:min(62%,240px)}}

HTML

<p class="jf-hint">גלול. הפריט עובר בין המסגרות במקום להתחלף.</p>
<div class="jf-wrap">
  <section class="jf-step"><div class="jf-copy"><div class="jf-num">שלב 01</div><h3>שיחת אפיון</h3>
    <p>מבינים את העסק, את הלקוח ואת מה שצריך לקרות באתר, לפני שנוגעים בעיצוב.</p></div>
    <div class="ph jf-zone ph-a" style="opacity:0"></div></section>
  <section class="jf-step"><div class="jf-copy"><div class="jf-num">שלב 02</div><h3>קופי ומבנה</h3>
    <p>כותבים את המסרים ובונים את סדר הסקשנים, כך שכל גלילה עונה על השאלה הבאה.</p></div>
    <div class="ph jf-zone ph-a" style="opacity:0"></div></section>
  <section class="jf-step"><div class="jf-copy"><div class="jf-num">שלב 03</div><h3>עיצוב ופיתוח</h3>
    <p>הופכים את המבנה לאתר חי, מהיר, שנראה נכון בכל מסך.</p></div>
    <div class="ph jf-zone ph-a" style="opacity:0"></div></section>
  <section class="jf-step"><div class="jf-copy"><div class="jf-num">שלב 04</div><h3>עלייה ומדידה</h3>
    <p>מחברים מעקב, עולים לאוויר, ובודקים מה עובד במקום לנחש.</p></div>
    <div class="ph jf-zone ph-a" style="opacity:0"></div></section>
  <div class="ph jf-trav ph-a"><span class="ph-l">◆</span></div>
</div>

JS

gsap.registerPlugin(Flip, ScrollTrigger);

(function(){
  const trav=document.querySelector(".jf-trav");
  const zones=gsap.utils.toArray(".jf-zone");
  const reduce=matchMedia("(prefers-reduced-motion: reduce)").matches;
  if(reduce){zones.forEach(z=>z.style.opacity=1);trav.style.display="none";return;}
  let tl;
  function build(){
    if(tl){tl.scrollTrigger&&tl.scrollTrigger.kill();tl.kill();gsap.set(trav,{clearProps:"all"});}
    Flip.fit(trav,zones[0]);                       // מתחילים מוצמדים לתחנה הראשונה
    tl=gsap.timeline({scrollTrigger:{
      trigger:zones[0],start:"center center",
      endTrigger:zones[zones.length-1],end:"center center",scrub:.6
    }});
    zones.slice(1).forEach((z,i)=>{
      // המשך הנסיעה נמדד במרחק אמיתי בין התחנות, ולכן הקצב אחיד
      const dist=z.getBoundingClientRect().top-zones[i].getBoundingClientRect().top;
      const last=i===zones.length-2;                 // בתחנה האחרונה מתיישרים, כדי שהסיום ייראה מכוון
      tl.add(Flip.fit(trav,z,{duration:Math.abs(dist)||1,ease:"power2.inOut",rotate:last?0:(i%2?8:-8)}));
    });
  }
  build();
  let t;addEventListener("resize",()=>{clearTimeout(t);t=setTimeout(()=>{build();ScrollTrigger.refresh();},250);});
})();