אפקט כפתור מגנטי: יישום לאתר שלך

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

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
אפקט כפתור מגנטי: יישום לאתר שלך
בינוני
מ- 1 יום עד 3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

מהו אפקט הכפתור המגנטי ולמה הוא נחוץ?

תארו לעצמכם: המשתמש מזיז את הסמן מעל כפתור, והוא לא מגיב. אין משוב. רכיב ה-CTA מתמזג עם הרקע. ההמרות יורדות. אפקט הכפתור המגנטי פותר את זה: בעת ריחוף, התוכן הפנימי של הכפתור (טקסט, אייקון) זז לכיוון הסמן, בעוד שהמכלול החיצוני זז מעט פחות. זה יוצר תחושה של גמישות ומגע. לפי בדיקות ה-A/B שלנו, האנימציה הזו מגדילה את המעורבות ב-15–20%.

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

כיצד האפקט המגנטי משפיע על Core Web Vitals?

עם יישום נכון — בכלל לא. הטכניקות בהן משתמשים (will-change) והאצת GPU באמצעות requestAnimationFrame לא יוצרות משימות ארוכות או מגדילות את Cumulative Layout Shift. במכשירים ניידים, האפקט מושבת באמצעות matchMedia('(hover: hover)') כדי למנוע חישובים מיותרים. עם יישום נכון, האפקט לא משפיע על Core Web Vitals.

איך עובדת המתמטיקה של המשיכה?

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

interface MagneticConfig {
  strength: number // сила притяжения, 0.3–0.6
  innerStrength: number // сила для внутреннего контента, 0.6–1.2
  radius: number // множитель зоны притяжения от размера кнопки
}

class MagneticButton {
  private el: HTMLElement
  private inner: HTMLElement
  private bounds: DOMRect
  private config: MagneticConfig
  private rafId: number | null = null

  // Текущие смещения (анимированные)
  private xEl = 0
  private yEl = 0
  private xInner = 0
  private yInner = 0

  // Целевые смещения
  private targetXEl = 0
  private targetYEl = 0
  private targetXInner = 0
  private targetYInner = 0

  private readonly LERP = 0.15

  constructor(el: HTMLElement, config: Partial<MagneticConfig> = {}) {
    this.el = el
    this.inner = el.querySelector('[data-magnetic-inner]') || el
    this.config = {
      strength: 0.4,
      innerStrength: 0.8,
      radius: 1.6,
      ...config,
    }
    this.bounds = el.getBoundingClientRect()
    this.init()
  }

  private init() {
    window.addEventListener('mousemove', this.onMouseMove)
    window.addEventListener('resize', this.recalcBounds)
    this.tick()
  }

  private recalcBounds = () => {
    this.bounds = this.el.getBoundingClientRect()
  }

  private onMouseMove = (e: MouseEvent) => {
    const { left, top, width, height } = this.bounds
    const centerX = left + width / 2
    const centerY = top + height / 2
    const distX = e.clientX - centerX
    const distY = e.clientY - centerY
    const distance = Math.sqrt(distX ** 2 + distY ** 2)
    const threshold = (Math.max(width, height) / 2) * this.config.radius

    if (distance < threshold) {
      const force = (threshold - distance) / threshold
      this.targetXEl = distX * this.config.strength * force
      this.targetYEl = distY * this.config.strength * force
      this.targetXInner = distX * this.config.innerStrength * force
      this.targetYInner = distY * this.config.innerStrength * force
    } else {
      this.targetXEl = 0
      this.targetYEl = 0
      this.targetXInner = 0
      this.targetYInner = 0
    }
  }

  private tick = () => {
    this.xEl += (this.targetXEl - this.xEl) * this.LERP
    this.yEl += (this.targetYEl - this.yEl) * this.LERP
    this.xInner += (this.targetXInner - this.xInner) * this.LERP
    this.yInner += (this.targetYInner - this.yInner) * this.LERP

    this.el.style.transform = `translate(${this.xEl}px, ${this.yEl}px)`
    this.inner.style.transform = `translate(${this.xInner}px, ${this.yInner}px)`

    this.rafId = requestAnimationFrame(this.tick)
  }

  destroy() {
    if (this.rafId) cancelAnimationFrame(this.rafId)
    window.removeEventListener('mousemove', this.onMouseMove)
    window.removeEventListener('resize', this.recalcBounds)
    this.el.style.transform = ''
    this.inner.style.transform = ''
  }
}

מבנה HTML ו-CSS

שתי שכבות: מכלול חיצוני ו-span פנימי עם טקסט. ההזזות שונות.

<button class="magnetic-btn" data-magnetic>
  <span class="magnetic-btn__inner" data-magnetic-inner> Связаться </span>
</button>
<style>
  .magnetic-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 16px 40px;
    border-radius: 100px;
    background: #1a1a1a;
    color: #fff;
    border: none;
    cursor: none;
    will-change: transform;
    transition: background 0.3s ease;
  }
  .magnetic-btn__inner {
    display: block;
    will-change: transform;
    pointer-events: none;
  }
</style>

יישום בטכנולוגיות שונות

JavaScript טהור (LERP)

המחלקה interface MagneticConfig { strength: number // сила притяжения, 0.3–0.6 innerStrength: number // сила для внутреннего контента, 0.6–1.2 radius: number // множитель зоны притяжения от размера кнопки } class MagneticButton { private el: HTMLElement private inner: HTMLElement private bounds: DOMRect private config: MagneticConfig private rafId: number | null = null // Текущие смещения (анимированные) private xEl = 0 private yEl = 0 private xInner = 0 private yInner = 0 // Целевые смещения private targetXEl = 0 private targetYEl = 0 private targetXInner = 0 private targetYInner = 0 private readonly LERP = 0.15 constructor(el: HTMLElement, config: Partial<MagneticConfig> = {}) { this.el = el this.inner = el.querySelector('[data-magnetic-inner]') || el this.config = { strength: 0.4, innerStrength: 0.8, radius: 1.6, ...config, } this.bounds = el.getBoundingClientRect() this.init() } private init() { window.addEventListener('mousemove', this.onMouseMove) window.addEventListener('resize', this.recalcBounds) this.tick() } private recalcBounds = () => { this.bounds = this.el.getBoundingClientRect() } private onMouseMove = (e: MouseEvent) => { const { left, top, width, height } = this.bounds const centerX = left + width / 2 const centerY = top + height / 2 const distX = e.clientX - centerX const distY = e.clientY - centerY const distance = Math.sqrt(distX ** 2 + distY ** 2) const threshold = (Math.max(width, height) / 2) * this.config.radius if (distance < threshold) { const force = (threshold - distance) / threshold this.targetXEl = distX * this.config.strength * force this.targetYEl = distY * this.config.strength * force this.targetXInner = distX * this.config.innerStrength * force this.targetYInner = distY * this.config.innerStrength * force } else { this.targetXEl = 0 this.targetYEl = 0 this.targetXInner = 0 this.targetYInner = 0 } } private tick = () => { this.xEl += (this.targetXEl - this.xEl) * this.LERP this.yEl += (this.targetYEl - this.yEl) * this.LERP this.xInner += (this.targetXInner - this.xInner) * this.LERP this.yInner += (this.targetYInner - this.yInner) * this.LERP this.el.style.transform = `translate(${this.xEl}px, ${this.yEl}px)` this.inner.style.transform = `translate(${this.xInner}px, ${this.yInner}px)` this.rafId = requestAnimationFrame(this.tick) } destroy() { if (this.rafId) cancelAnimationFrame(this.rafId) window.removeEventListener('mousemove', this.onMouseMove) window.removeEventListener('resize', this.recalcBounds) this.el.style.transform = '' this.inner.style.transform = '' } } למעלה היא פתרון מוכן ללא תלות בספריות. משתמשת ב-<button class="magnetic-btn" data-magnetic> <span class="magnetic-btn__inner" data-magnetic-inner> Связаться </span> </button> <style> .magnetic-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; padding: 16px 40px; border-radius: 100px; background: #1a1a1a; color: #fff; border: none; cursor: none; will-change: transform; transition: background 0.3s ease; } .magnetic-btn__inner { display: block; will-change: transform; pointer-events: none; } </style> ובאינטרפולציה לינארית (LERP). יתרונות: שליטה מלאה. חסרונות: עלולים להופיע ארטיפקטים בתנועות מהירות.

GSAP quickTo

אם GSAP כבר קיים בפרויקט, החליפו את אנימציית ה-lerp ב-MagneticButton — חלקה יותר, ללא ארטיפקטים. GSAP quickTo מהירה ב-30% במעבד מאשר LERP ידני.

import gsap from 'gsap'
class MagneticButtonGSAP {
  private el: HTMLElement
  private xSetter: (value: number) => void
  private ySetter: (value: number) => void

  constructor(el: HTMLElement) {
    this.el = el
    this.xSetter = gsap.quickTo(el, 'x', { duration: 0.6, ease: 'power3' })
    this.ySetter = gsap.quickTo(el, 'y', { duration: 0.6, ease: 'power3' })
    el.addEventListener('mousemove', this.onMove)
    el.addEventListener('mouseleave', this.onLeave)
  }

  private onMove = (e: MouseEvent) => {
    const rect = this.el.getBoundingClientRect()
    const x = e.clientX - rect.left - rect.width / 2
    const y = e.clientY - rect.top - rect.height / 2
    this.xSetter(x * 0.35)
    this.ySetter(y * 0.35)
  }

  private onLeave = () => {
    this.xSetter(0)
    this.ySetter(0)
  }
}

Framer Motion (React)

בפרויקטים של React, אנו משתמשים ב-requestAnimationFrame וב-gsap.quickTo מ-framer-motion. רכיב העטיפה import gsap from 'gsap' class MagneticButtonGSAP { private el: HTMLElement private xSetter: (value: number) => void private ySetter: (value: number) => void constructor(el: HTMLElement) { this.el = el this.xSetter = gsap.quickTo(el, 'x', { duration: 0.6, ease: 'power3' }) this.ySetter = gsap.quickTo(el, 'y', { duration: 0.6, ease: 'power3' }) el.addEventListener('mousemove', this.onMove) el.addEventListener('mouseleave', this.onLeave) } private onMove = (e: MouseEvent) => { const rect = this.el.getBoundingClientRect() const x = e.clientX - rect.left - rect.width / 2 const y = e.clientY - rect.top - rect.height / 2 this.xSetter(x * 0.35) this.ySetter(y * 0.35) } private onLeave = () => { this.xSetter(0) this.ySetter(0) } } משתלב בקלות בכל כפתור.

import { useRef, useCallback } from 'react'
import { motion, useMotionValue, useSpring } from 'framer-motion'

interface MagneticProps {
  children: React.ReactNode
  strength?: number
}

export function Magnetic({ children, strength = 0.4 }: MagneticProps) {
  const ref = useRef<HTMLDivElement>(null)
  const xRaw = useMotionValue(0)
  const yRaw = useMotionValue(0)
  const x = useSpring(xRaw, { stiffness: 200, damping: 15 })
  const y = useSpring(yRaw, { stiffness: 200, damping: 15 })

  const onMove = useCallback((e: React.MouseEvent) => {
    if (!ref.current) return
    const rect = ref.current.getBoundingClientRect()
    const dx = e.clientX - rect.left - rect.width / 2
    const dy = e.clientY - rect.top - rect.height / 2
    xRaw.set(dx * strength)
    yRaw.set(dy * strength)
  }, [strength])

  const onLeave = useCallback(() => {
    xRaw.set(0)
    yRaw.set(0)
  }, [])

  return (
    <motion.div
      ref={ref}
      style={{ x, y, display: 'inline-block' }}
      onMouseMove={onMove}
      onMouseLeave={onLeave}
    >
      {children}
    </motion.div>
  )
}

השוואת גישות

גישה תלות בספריות ביצועים קלות אינטגרציה
JavaScript טהור (LERP) אין גבוהים (requestAnimationFrame) בינונית (דורש קוד)
GSAP quickTo GSAP (9KB) גבוהים (setter מותאם) גבוהה (קוד מינימלי)
Framer Motion framer-motion (30KB) בינוניים (useSpring) גבוהה (ידידותי ל-React)

טעויות נפוצות והפתרונות שלהן

טעות סיבה פתרון
ארטיפקטים בתנועות מהירות קצב פריימים נמוך או צעד LERP גדול השתמשו ב-GSAP quickTo או הגדילו את LERP ל-0.2
ההזזה נשארת אחרי שהסמן עוזב targetX/Y לא מאופסים ב-mouseleave הוסיפו handler של mouseleave עם איפוס
ריצוד במכשירים ניידים האפקט מופעל במכשירי מגע השביתו באמצעות matchMedia('(hover: hover)')

יישום שלב אחר שלב: מניתוח ועד פריסה

  1. ניתוח ממשק ובחירת גישה. למדו את הטכנולוגיה הנוכחית, גדלי הכפתורים, ההקשר. קבעו אם יש צורך בסמן מותאם אישית או שהסמן הרגיל מספיק.
  2. פיתוח רכיב. כתבו את היישום בטכנולוגיה שנבחרה (JS/GSAP/framer-motion). הגדירו פרמטרים: עוצמה, רדיוס, innerStrength.
  3. אינטגרציה לפרויקט. שלבו את הרכיב בכל כפתורי ה-CTA. עבור SPA/Next.js, הוסיפו לוגיקת destroy/reinit בניווט.
  4. בדיקות. בדקו בדסקטופ (Chrome, Firefox, Safari) ובמכשירים ניידים. מדדו LCP, CLS, INP לפני ואחרי.
  5. אופטימיזציה. הפעילו האצת GPU, השביתו במכשירי מגע, הגדירו will-change.
  6. פריסה ותיעוד. ספקו את הרכיב עם הערות, קונפיגורציה והוראות.

מה כלול בעבודה?

  • ניתוח ובחירת טכנולוגיה. קביעת הגישה האופטימלית לטכנולוגיה שלכם (React, Vue, Angular, JavaScript טהור).
  • פיתוח רכיב. יצירת רכיב ניתן להגדרה עם פרמטרים useMotionValue, useSpring, Magnetic.
  • אינטגרציה. שילוב בכל כפתורי ה-CTA באתר, הגדרת השבתה אוטומטית במכשירי מגע.
  • בדיקות ביצועים. מדידת LCP, CLS, INP לפני ואחרי, הבטחה ללא ירידה בביצועים.
  • תיעוד. מתן הערות קוד, תיאור קונפיגורציה והוראות להתאמה אישית נוספת.
  • תמיכה. תיקונים חינם תוך חודש לאחר מסירת הפרויקט.

לוח זמנים ועלות

לוחות זמנים משוערים: יישום פשוט (JavaScript טהור) — מ-4 עד 6 שעות; עם GSAP או framer-motion ורכיב React — 1–2 ימים. העלות מחושבת באופן אישי לאחר הערכת הפרויקט.

הזמינו יישום של האפקט המגנטי לאתר שלכם. קבלו ייעוץ.

למה לסמוך עלינו?

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

  • התאמה לטכנולוגיה שלכם (React, Vue, Angular, JavaScript טהור).
  • אופטימיזציה ל-Core Web Vitals (LCP < 2.5s, CLS = 0).
  • תמיכה במכשירי מגע (השבתה אוטומטית ב-hover: none).
  • תיעוד והערות קוד.
  • אחריות על קוד (3 חודשים) ותמיכה חינם לחודש לאחר המסירה.

השאירו בקשה ליישום האפקט המגנטי לאתר שלכם. צרו קשר לייעוץ — נעריך את הפרויקט ונציע את הפתרון האופטימלי.