→ לכל המאגר

G43 · רשימת פרויקטים עם תצוגה מקדימה שעוקבת אחרי הסמן

GSAP GSAP · quickTo תמונות וגלריותhover ומיקרוכרטיסים ממתין

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

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

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

הדמו

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

01

מותג לחברת נדל"ן

מיתוג · אתר
02

חנות תכשיטים

איקומרס
03

קליניקה פרטית

אתר · קופי
04

אפליקציית כושר

מוצר
05

יקב בוטיק

מיתוג
1
2
3
4
5
שני פרטים שעושים את ההבדל: השורות שאינן מרחפות דוהות ל-35 אחוז כך שהעין ננעלת על אחת, והחלונית נעצרת בשולי המסך במקום להיחתך. במובייל המהלך כבוי לגמרי, כי אין שם hover והשורות צריכות להישאר קריאות.

קוד להדבקה

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

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

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-l{background:rgba(16,18,43,.72);color:#fff;padding:.18em .7em;border-radius:999px;line-height:1.3;font-size:max(.78em,12px)}

/* G43 · רשימת פרויקטים עם תצוגה מקדימה שעוקבת אחרי הסמן */
.pl{padding-inline:var(--gutter, clamp(24px, 5vw, 96px));position:relative}
.pl-row{display:flex;align-items:baseline;gap:18px;padding:clamp(18px,2.2vw,32px) 4px;border-top:1px solid var(--line, #e4e4ee);cursor:pointer;position:relative}
.pl-row:last-child{border-bottom:1px solid var(--line, #e4e4ee)}
.pl-row .idx{font-size:13px;color:var(--muted, #6a6d85);font-variant-numeric:tabular-nums;min-width:3ch}
.pl-row h3{margin:0;font-size:clamp(24px,3vw,50px);font-weight:800;transition:transform .4s cubic-bezier(.2,.6,.2,1),opacity .3s}
.pl-row .tags{margin-inline-start:auto;font-size:12px;letter-spacing:.1em;color:var(--muted, #6a6d85)}
.pl.hovering .pl-row h3{opacity:.35}
.pl-row.on h3{opacity:1;transform:translateX(-16px)}
/* עוגן פיזי ולא לוגי: חשבון המיקום נעשה בקואורדינטות המסך, וב-RTL עוגן לוגי היה מזיז אותו לקצה הימני */
.pl-prev{position:fixed;top:0;left:0;width:min(330px,34vw);aspect-ratio:4/3;border-radius:12px;overflow:hidden;
  pointer-events:none;z-index:20;opacity:0;transform:scale(.94);will-change:transform;box-shadow:0 24px 60px rgba(0,0,0,.22)}
.pl-prev .ph{position:absolute;inset:0;font-size:28px;opacity:0}
.pl-prev .ph.on{opacity:1}
.pl-hint{color:var(--muted, #6a6d85);font-size:14px;padding-inline:var(--gutter, clamp(24px, 5vw, 96px));margin-bottom:10px}
@media(max-width:767px){.pl-prev{display:none}.pl-row .tags{display:none}.pl.hovering .pl-row h3{opacity:1}}

HTML

<div class="stage tight">
<p class="pl-hint">העבר עכבר על השורות. במובייל התצוגה המקדימה מוסתרת והרשימה נשארת רשימה.</p>
<div class="pl">
  <div class="pl-row" data-i="0"><span class="idx">01</span><h3>מותג לחברת נדל"ן</h3><span class="tags">מיתוג · אתר</span></div>
  <div class="pl-row" data-i="1"><span class="idx">02</span><h3>חנות תכשיטים</h3><span class="tags">איקומרס</span></div>
  <div class="pl-row" data-i="2"><span class="idx">03</span><h3>קליניקה פרטית</h3><span class="tags">אתר · קופי</span></div>
  <div class="pl-row" data-i="3"><span class="idx">04</span><h3>אפליקציית כושר</h3><span class="tags">מוצר</span></div>
  <div class="pl-row" data-i="4"><span class="idx">05</span><h3>יקב בוטיק</h3><span class="tags">מיתוג</span></div>
</div>
<div class="pl-prev">
  <div class="ph ph-a"><span class="ph-l">1</span></div><div class="ph ph-b"><span class="ph-l">2</span></div><div class="ph ph-c"><span class="ph-l">3</span></div><div class="ph ph-d"><span class="ph-l">4</span></div><div class="ph ph-e"><span class="ph-l">5</span></div>
</div></div>

JS

(function(){
  const list=document.querySelector(".pl"),prev=document.querySelector(".pl-prev");
  const rows=[...list.querySelectorAll(".pl-row")],shots=[...prev.querySelectorAll(".ph")];
  if(!matchMedia("(hover:hover)").matches)return;
  // quickTo נותן מעקב רך אחרי הסמן בלי ליצור טווין חדש בכל אירוע
  const xTo=gsap.quickTo(prev,"x",{duration:.55,ease:"power3"});
  const yTo=gsap.quickTo(prev,"y",{duration:.55,ease:"power3"});
  const w=()=>prev.offsetWidth,h=()=>prev.offsetHeight;
  let shown=false,placed=false;
  function place(e){
    // בעברית הסמן קורא ימינה-שמאלה, ולכן החלונית יושבת משמאל לסמן
    let x=e.clientX-w()-26;
    if(x<12)x=e.clientX+26;
    let y=gsap.utils.clamp(12,window.innerHeight-h()-12,e.clientY-h()/2);
    // המיקום הראשון נקבע מיידית, אחרת החלונית נפתחת בפינת המסך וטסה משם
    if(!placed){placed=true;gsap.set(prev,{x:x,y:y});}
    xTo(x);yTo(y);
  }
  list.addEventListener("pointermove",place);
  rows.forEach(r=>{
    r.addEventListener("pointerenter",()=>{
      rows.forEach(x=>x.classList.toggle("on",x===r));
      shots.forEach((s,i)=>s.classList.toggle("on",i===+r.dataset.i));
      if(!shown){
        shown=true;list.classList.add("hovering");
        gsap.to(prev,{opacity:1,scale:1,duration:.35,ease:"power3.out"});
      }
    });
  });
  list.addEventListener("pointerleave",()=>{
    shown=false;list.classList.remove("hovering");
    rows.forEach(x=>x.classList.remove("on"));
    gsap.to(prev,{opacity:0,scale:.94,duration:.25,ease:"power2.in"});
  });
})();