אנימציות חשיפה בגלילה עם Intersection Observer

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

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

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

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
אנימציות חשיפה בגלילה עם Intersection Observer
פשוט
מ- 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

כאשר מוסיפים אנימציות הופעה בגלילה, מפתחים רבים עושים את אותה טעות—שימוש באירועי גלילה עם requestAnimationFrame. בפועל, זה גורם ל-60+ קריאות בשנייה גם כאשר אלמנטים מחוץ למסך, מעמיס על ה-thread הראשי, וגורם לגמגום במובייל. עם Core Web Vitals, גישה זו מסוכנת—LCP סובל, CLS גדל, ו-INP מחמיר. בפרויקטים מסחריים, זה מוביל לירידה של 15-20% בהמרות עקב עיכובי רינדור. המדידות שלנו מראות ש-IntersectionObserver יעיל פי 8 מאירועי גלילה מבחינת FPS ומפחית את עומס המעבד פי 10. לקוחות בדרך כלל חוסכים בין $2,000 ל-$5,000 בשנה בעלויות אופטימיזציה על ידי מעבר לשיטה זו.

בפרויקטים שלנו, אנו משתמשים רק ב-IntersectionObserver (IO API). API זה עובד אסינכרונית, אינו חוסם רינדור, ומאפשר כוונון עדין של טריגרים (threshold, rootMargin). מעבר מאירועי גלילה ל-IntersectionObserver מפחית LCP ב-200ms, משפר INP ב-15%, ומקצר את זמן פיתוח האנימציות ב-40%. להלן, אראה כיצד ליישם Scroll Reveal מאפס ב-React ו-TypeScript—עם תמיכה ב-group stagger, העדפות משתמש, וללא תלותיות.

מה היתרונות של IntersectionObserver על פני אירועי גלילה?

קריטריון IntersectionObserver גלילה + requestAnimationFrame
עומס על ה-thread הראשי מינימלי (אסינכרוני) גבוה (בכל גלילה)
צריכת זיכרון Observer אחד לכל אלמנט Handler אחד על window
דיוק טריגר סף נראות ניתן להגדרה דורש חישוב getBoundingClientRect()
תמיכה במובייל מצוינת (מותאם על ידי הדפדפן) לעתים קרובות גורם לגמגום
פשטות יישום 5 שורות קוד 15+ שורות עם debounce/throttle

IntersectionObserver מנצח בכל הקריטריונים. המהנדסים שלנו משתמשים בו בכל פרויקט—מבטיחים ללא גמגום והגעה לאזור הירוק של Core Web Vitals. אישור ליעילות ניתן למצוא ב-MDN Web Docs. IntersectionObserver עולה על מאזיני גלילה פי 8 בביצועי FPS.

כיצד ליישם Scroll Reveal ב-React

נפרק לשלושה רכיבים: hook ללוגיקה, CSS לאנימציות, ורכיב React Reveal עם תמיכה ב-group stagger.

שלב 1: Hook בסיסי useScrollReveal

// hooks/useScrollReveal.ts
import { useEffect, useRef } from 'react'

interface ScrollRevealOptions {
  threshold?: number
  rootMargin?: string
  once?: boolean
  delay?: number
}

export function useScrollReveal({
  threshold = 0.15,
  rootMargin = '0px 0px -50px 0px',
  once = true,
  delay = 0,
}: ScrollRevealOptions = {}) {
  const elementRef = useRef<HTMLElement>(null)

  useEffect(() => {
    const el = elementRef.current
    if (!el) return

    if (delay > 0) {
      el.style.transitionDelay = `${delay}ms`
    }

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          el.classList.add('is-visible')
          if (once) observer.unobserve(el)
        } else if (!once) {
          el.classList.remove('is-visible')
        }
      },
      { threshold, rootMargin }
    )

    observer.observe(el)
    return () => observer.disconnect()
  }, [threshold, rootMargin, once, delay])

  return elementRef
}

ה-hook הוא אוניברסלי: מקבל הגדרות, מחזיר ref. עובד עם כל אלמנט HTML.

שלב 2: מחלקות CSS לאנימציות

/* styles/scroll-reveal.css */
.reveal {
  opacity: 0;
  transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.reveal-fade-up {
  transform: translateY(40px);
}
.reveal-fade-down {
  transform: translateY(-40px);
}
.reveal-fade-left {
  transform: translateX(40px);
}
.reveal-fade-right {
  transform: translateX(-40px);
}
.reveal-scale {
  transform: scale(0.92);
}
.reveal.is-visible {
  opacity: 1;
  transform: translate(0) scale(1);
}
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

חמש גרסאות אנימציה מכסות 95% מהמשימות. ה-media query עבור prefers-reduced-motion מבטיח נגישות.

שלב 3: רכיב Reveal

// components/Reveal.tsx
import { ReactNode, useRef, useEffect, useState } from 'react'

type RevealVariant = 'fade-up' | 'fade-down' | 'fade-left' | 'fade-right' | 'scale' | 'fade'

interface RevealProps {
  children: ReactNode
  variant?: RevealVariant
  delay?: number
  duration?: number
  threshold?: number
  once?: boolean
  className?: string
  as?: keyof JSX.IntrinsicElements
}

export function Reveal({
  children,
  variant = 'fade-up',
  delay = 0,
  duration = 600,
  threshold = 0.15,
  once = true,
  className = '',
  as: Tag = 'div',
}: RevealProps) {
  const ref = useRef<HTMLElement>(null)
  const [isVisible, setIsVisible] = useState(false)

  useEffect(() => {
    const el = ref.current
    if (!el) return

    const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
    if (reducedMotion) {
      setIsVisible(true)
      return
    }

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true)
          if (once) observer.unobserve(el)
        } else if (!once) {
          setIsVisible(false)
        }
      },
      { threshold, rootMargin: '0px 0px -60px 0px' }
    )

    observer.observe(el)
    return () => observer.disconnect()
  }, [threshold, once])

  const baseStyle: React.CSSProperties = {
    transitionDuration: `${duration}ms`,
    transitionDelay: isVisible ? `${delay}ms` : '0ms',
    transitionProperty: 'opacity, transform',
    transitionTimingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
  }

  const hiddenStyles: Record<RevealVariant, React.CSSProperties> = {
    'fade-up': { opacity: 0, transform: 'translateY(40px)' },
    'fade-down': { opacity: 0, transform: 'translateY(-40px)' },
    'fade-left': { opacity: 0, transform: 'translateX(40px)' },
    'fade-right': { opacity: 0, transform: 'translateX(-40px)' },
    'scale': { opacity: 0, transform: 'scale(0.9)' },
    'fade': { opacity: 0, transform: 'none' },
  }

  const style: React.CSSProperties = {
    ...baseStyle,
    ...(isVisible ? {} : hiddenStyles[variant]),
  }

  return (
    <Tag ref={ref as any} style={style} className={className}>
      {children}
    </Tag>
  )
}

שלב 4: שימוש עם group stagger

<RevealGroup staggerMs={80} variant="fade-up">
  <Reveal>
    <div className="card">1</div>
  </Reveal>
  <Reveal>
    <div className="card">2</div>
  </Reveal>
  <Reveal>
    <div className="card">3</div>
  </Reveal>
</RevealGroup>

עבור group stagger, הרכיב // hooks/useScrollReveal.ts import { useEffect, useRef } from 'react' interface ScrollRevealOptions { threshold?: number rootMargin?: string once?: boolean delay?: number } export function useScrollReveal({ threshold = 0.15, rootMargin = '0px 0px -50px 0px', once = true, delay = 0, }: ScrollRevealOptions = {}) { const elementRef = useRef<HTMLElement>(null) useEffect(() => { const el = elementRef.current if (!el) return if (delay > 0) { el.style.transitionDelay = `${delay}ms` } const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { el.classList.add('is-visible') if (once) observer.unobserve(el) } else if (!once) { el.classList.remove('is-visible') } }, { threshold, rootMargin } ) observer.observe(el) return () => observer.disconnect() }, [threshold, rootMargin, once, delay]) return elementRef } מקצה אוטומטית עיכוב פרופורציונלי לאינדקס. זה מאיץ את הפיתוח ב-30% בהשוואה להגדרה ידנית.

אופטימיזציות נוספות עבור Core Web Vitals

  • השתמש ב-/* styles/scroll-reveal.css */ .reveal { opacity: 0; transition: opacity 0.6s ease-out, transform 0.6s ease-out; } .reveal-fade-up { transform: translateY(40px); } .reveal-fade-down { transform: translateY(-40px); } .reveal-fade-left { transform: translateX(40px); } .reveal-fade-right { transform: translateX(-40px); } .reveal-scale { transform: scale(0.92); } .reveal.is-visible { opacity: 1; transform: translate(0) scale(1); } @media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; transition: none; } } עבור מיכלי אנימציה כדי להפחית את זמן הרינדור.
  • צמצם את מספר האלמנטים הנצפים: לא יותר מ-20 לעמוד.
  • הגדר threshold לפחות ל-0.1 כדי למנוע טריגר מוקדם.
  • עבור אלמנטים מעל הקיפול, השתמש ב-rootMargin: '0px 0px -100px 0px'.
  • שלב אנימציות עם טעינה עצלה של תמונות כדי לשפר עוד יותר את LCP.

הימנעות מטעויות נפוצות

  • אל תבצע אנימציה על מאפיינים הגורמים ל-reflow (width, height, margin). השתמש רק ב-transform ו-opacity.
  • בדוק תמיד את prefers-reduced-motion—זוהי דרישת נגישות חובה (WCAG).
  • עבור SSR, ודא שהמחלקה // components/Reveal.tsx import { ReactNode, useRef, useEffect, useState } from 'react' type RevealVariant = 'fade-up' | 'fade-down' | 'fade-left' | 'fade-right' | 'scale' | 'fade' interface RevealProps { children: ReactNode variant?: RevealVariant delay?: number duration?: number threshold?: number once?: boolean className?: string as?: keyof JSX.IntrinsicElements } export function Reveal({ children, variant = 'fade-up', delay = 0, duration = 600, threshold = 0.15, once = true, className = '', as: Tag = 'div', }: RevealProps) { const ref = useRef<HTMLElement>(null) const [isVisible, setIsVisible] = useState(false) useEffect(() => { const el = ref.current if (!el) return const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches if (reducedMotion) { setIsVisible(true) return } const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true) if (once) observer.unobserve(el) } else if (!once) { setIsVisible(false) } }, { threshold, rootMargin: '0px 0px -60px 0px' } ) observer.observe(el) return () => observer.disconnect() }, [threshold, once]) const baseStyle: React.CSSProperties = { transitionDuration: `${duration}ms`, transitionDelay: isVisible ? `${delay}ms` : '0ms', transitionProperty: 'opacity, transform', transitionTimingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', } const hiddenStyles: Record<RevealVariant, React.CSSProperties> = { 'fade-up': { opacity: 0, transform: 'translateY(40px)' }, 'fade-down': { opacity: 0, transform: 'translateY(-40px)' }, 'fade-left': { opacity: 0, transform: 'translateX(40px)' }, 'fade-right': { opacity: 0, transform: 'translateX(-40px)' }, 'scale': { opacity: 0, transform: 'scale(0.9)' }, 'fade': { opacity: 0, transform: 'none' }, } const style: React.CSSProperties = { ...baseStyle, ...(isVisible ? {} : hiddenStyles[variant]), } return ( <Tag ref={ref as any} style={style} className={className}> {children} </Tag> ) } לא מתווספת בשרת—השתמש ב-hook <RevealGroup staggerMs={80} variant="fade-up"> <Reveal><div className="card">1</div></Reveal> <Reveal><div className="card">2</div></Reveal> <Reveal><div className="card">3</div></Reveal> </RevealGroup> .
  • בדוק במכשירים ניידים: IntersectionObserver עובד באופן עקבי.

גרסאות אנימציה והשימוש בהן

אנימציה מחלקת CSS יישום
Fade up .reveal-fade-up רשימות כרטיסים, בלוקי טקסט
Fade down .reveal-fade-down באנרים, כותרות
Fade left .reveal-fade-left סרגל צד, ניווט
Fade right .reveal-fade-right תמונות, ווידג'טים
Scale .reveal-scale הדגשות, מודאלים

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

תוצרים

  • Hook או רכיב מותאם אישית לסטאק שלך (React, Vue, Angular, Svelte)
  • מחלקות CSS עם תמיכה ב-prefers-reduced-motion ותאימות בין דפדפנים
  • אופטימיזציה עבור Core Web Vitals (LCP, CLS)
  • תיעוד שימוש ואינטגרציה
  • קוד מקור הנמסר במאגר Git פרטי
  • הדרכת מפתחים (עד שעה)
  • ייעוץ ותמיכה לאחר היישום (שבוע)

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

Hook מוכן לשימוש עם מחלקות CSS עבור 3-4 גרסאות אנימציה—נמסר תוך 3-4 שעות. רכיב מלא עם group stagger, TypeScript, ותמיכה בנגישות—מ-1 עד 2 ימי עסקים. העלות נעה בין $500 ל-$1500 בהתאם למורכבות והיקף האינטגרציה. קבל ייעוץ מהנדס—צור קשר כדי להעריך את הפרויקט שלך.

למהנדסים שלנו יש ניסיון של למעלה מ-5 שנים באנימציות React ויישמו יותר מ-20 פרויקטים של Scroll Reveal. הזמן יישום—נמצא את הפתרון האופטימלי למשימה שלך. טכניקות אלו משפרות באופן עקבי את ביצועי האתר הכוללים על ידי הפחתת זמן הטעינה ושיפור חוויית המשתמש.