→ לכל המאגר

B54 · הובר שנכנס מהכיוון שממנו בא הסמן

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

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

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

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

הדמו

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

העבר עכבר מלמעלה, מהצד ומלמטה. השכבה נכנסת משם.

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

קוד להדבקה

הקוד כאן עומד בפני עצמו. כל טוקן עיצוב נושא ברירת מחדל, מחלקות העזר של המאגר מוטמעות בו, ותגי הסקריפט מסודרים לפי הסדר הנכון. פרויקט שמגדיר --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-f{background:linear-gradient(160deg,#c92a2a,#ff8787)}

/* B54 · הובר שנכנס מהכיוון שממנו בא הסמן */
.da{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gap, clamp(16px, 1.6vw, 36px));max-width:min(1020px,94vw);margin-inline:auto}
.da-card{position:relative;overflow:hidden;border-radius:16px;aspect-ratio:4/3;cursor:pointer;
  border:1px solid var(--line, #e4e4ee);background:var(--card, #fff)}
.da-card:focus-visible{outline:3px solid var(--accent, #4a3aff);outline-offset:3px}
.da-bg{position:absolute;inset:0;border-radius:0;font-size:0}
.da-name{position:absolute;inset-inline-start:16px;bottom:14px;z-index:2;color:#fff;font-weight:700;font-size:18px;
  text-shadow:0 2px 12px rgba(0,0,0,.45)}
.da-over{position:absolute;inset:0;z-index:1;background:rgba(74,58,255,.92);color:#fff;
  display:grid;place-content:center;text-align:center;padding:18px;gap:8px}
.da-over strong{font-size:20px}
.da-over span{font-size:14px;opacity:.9;max-width:24ch;line-height:1.6}
.da-hint{text-align:center;color:var(--muted, #6a6d85);font-size:14px;padding-top:22px}
@media(max-width:860px){.da{grid-template-columns:1fr 1fr}}
@media(max-width:560px){.da{grid-template-columns:1fr}}

HTML

<div class="stage tight">
<div class="da">
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-a"></div><span class="da-name">אתרי תדמית</span>
    <div class="da-over"><strong>אתרי תדמית</strong><span>אתר שמסביר מה אתם עושים ומוביל לפנייה אחת ברורה.</span></div></article>
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-c"></div><span class="da-name">דפי נחיתה</span>
    <div class="da-over"><strong>דפי נחיתה</strong><span>עמוד יחיד ממוקד לקמפיין, עם מסר אחד וקריאה אחת.</span></div></article>
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-d"></div><span class="da-name">חנויות</span>
    <div class="da-over"><strong>חנויות</strong><span>חנות שמוכרת גם בלי איש מכירות, עם מסלול קנייה קצר.</span></div></article>
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-e"></div><span class="da-name">מערכות</span>
    <div class="da-over"><strong>מערכות</strong><span>ממשק לניהול לקוחות, משימות ודוחות במקום גיליונות.</span></div></article>
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-b"></div><span class="da-name">מיתוג</span>
    <div class="da-over"><strong>מיתוג</strong><span>שפה חזותית אחת שחוזרת בכל נקודת מגע.</span></div></article>
  <article class="da-card" tabindex="0"><div class="ph da-bg ph-f"></div><span class="da-name">ליווי</span>
    <div class="da-over"><strong>ליווי</strong><span>אחזקה, מדידה ושיפור אחרי העלייה לאוויר.</span></div></article>
</div>
<p class="da-hint">העבר עכבר מלמעלה, מהצד ומלמטה. השכבה נכנסת משם.</p>
</div>

JS

(function(){
  const OFF=101;
  const reduce=matchMedia("(prefers-reduced-motion: reduce)").matches;
  // 0=למעלה 1=ימין 2=למטה 3=שמאל, בקואורדינטות פיזיות
  const VEC=[{x:0,y:-OFF},{x:OFF,y:0},{x:0,y:OFF},{x:-OFF,y:0}];
  function edge(el,e){
    const r=el.getBoundingClientRect();
    // מנרמלים לפי היחס בין הצלעות, אחרת בכרטיס רחב כל כניסה נחשבת "מהצד"
    const w=r.width,h=r.height;
    const x=(e.clientX-r.left-w/2)*(w>h?h/w:1);
    const y=(e.clientY-r.top-h/2)*(h>w?w/h:1);
    return (Math.round((Math.atan2(y,x)*(180/Math.PI)+180)/90)+3)%4;
  }
  document.querySelectorAll(".da-card").forEach(card=>{
    const over=card.querySelector(".da-over");
    gsap.set(over,{xPercent:0,yPercent:OFF});
    const show=v=>{gsap.killTweensOf(over);
      gsap.fromTo(over,{xPercent:v.x,yPercent:v.y},{xPercent:0,yPercent:0,duration:reduce?0:.42,ease:"power3.out"});};
    const hide=v=>{gsap.killTweensOf(over);
      gsap.to(over,{xPercent:v.x,yPercent:v.y,duration:reduce?0:.42,ease:"power3.in"});};
    card.addEventListener("mouseenter",e=>show(VEC[edge(card,e)]));
    card.addEventListener("mouseleave",e=>hide(VEC[edge(card,e)]));
    card.addEventListener("focus",()=>show(VEC[0]));      // מקלדת: תמיד מלמעלה
    card.addEventListener("blur",()=>hide(VEC[0]));
  });
})();