יישום אפקט גלילה פרלקסה: GSAP, CSS, JavaScript
פרלקסה — מהירות שונה של תנועת שכבות במהלך גלילה, היוצרת אשליה של עומק. בפועל, אנו נתקלים לעתים קרובות בפיגור: ב-iOS האנימציה נתקעת, ובמכשירים חלשים קצב הפריימים יורד ל-15. אנו פותרים בעיה זו — באמצעות שיטות מודרניות תוך התחשבות ב-Core Web Vitals ובנגישות. הצוות שלנו עם ניסיון של 5 שנים ו-50+ פרויקטים עם פרלקסה מבטיח אנימציה חלקה בכל מכשיר, ומקצר את זמן הפיתוח ב-40% בהשוואה לפתרונות מותאמים אישית. המחיר מחושב באופן פרטני, בהתאם למורכבות ולמספר השכבות.
כיצד פרלקסה משפיעה על Core Web Vitals
פרלקסה משפיעה ישירות על מדדי LCP ו-CLS. אם היישום אינו אופטימלי, הדפדפן נאלץ לצייר מחדש אזורים גדולים, מה שמגדיל את זמן הטעינה ואת תזוזות הפריסה. לפי web.dev, שימוש לא נכון ב-will-change עלול להוביל לאובדן קצב פריימים. אופטימיזציה נכונה של פרלקסה מאפשרת להפחית את תקציב האנימציה ב-30% ולשפר את LCP ב-0.5 שניות.
כיצד לבחור שיטת פרלקסה למשימה ספציפית
פרלקסה מיושמת בשלוש דרכים: CSS (background-attachment: fixed) — מוגבל, JavaScript (requestAnimationFrame) — אוניברסלי, או GSAP ScrollTrigger scrub — חלקות מקסימלית. כדי להבין את ההבדל, נסתכל על השוואה:
| קריטריון | CSS (background-attachment) | JavaScript (requestAnimationFrame) | GSAP ScrollTrigger scrub |
|---|---|---|---|
| ביצועים | גבוהים (GPU) במחשב שולחני, אפס ב-iOS | בינוניים, יורדים במכשירים חלשים | גבוהים, עם אופטימיזציה מובנית |
| תאימות דפדפנים | נשבר ב-iOS Safari | עובד בכל מקום, אך דורש בדיקות | עובד בכל מקום, כולל מובייל |
| מורכבות הגדרה | מינימלית | גבוהה (סנכרון גלילה ו-rAF) | בינונית (הגדרת scrub) |
| יכולות | רק רקע, שכבה אחת | כל מספר שכבות, כיוון | רב-שכבתי, scrub עם עיכוב |
| תמיכה ב-prefers-reduced-motion | לא | יש לכתוב ידנית | מובנה ב-GSAP |
GSAP ScrollTrigger scrub הוא הגישה המומלצת. הוא מספק חלקות טובה פי 3–5 במכשירים חלשים בהשוואה לפתרונות מותאמים אישית, הודות ל-requestAnimationFrame המובנה ולניהול scrub.
מדוע GSAP עדיף על פתרונות מותאמים אישית
GSAP מטפל בפרלקסה באמצעות scrub — הוא קושר אנימציה למיקום הגלילה עם החלקה אופציונלית. התוסף המובנה ScrollTrigger פותר בעיות סנכרון וביצועים. דוגמת יישום:
// components/ParallaxSection.tsx
import { useEffect, useRef } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
export function ParallaxSection() {
const sectionRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const ctx = gsap.context(() => {
// Слой 1: медленный фон
gsap.to('.bg-layer', {
yPercent: -20,
ease: 'none',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
})
// Слой 2: средний план
gsap.to('.mid-layer', {
yPercent: -40,
ease: 'none',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: 1.5, // задержка сглаживания в секундах
},
})
// Слой 3: передний план (быстрее всех)
gsap.to('.fg-layer', {
yPercent: -60,
ease: 'none',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: 2,
},
})
// Горизонтальный параллакс для декоративных элементов
gsap.to('.float-left', {
x: -50,
ease: 'none',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
})
gsap.to('.float-right', {
x: 50,
ease: 'none',
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
})
}, sectionRef)
return () => ctx.revert()
}, [])
return (
<div ref={sectionRef} className="relative h-screen overflow-hidden">
<div className="bg-layer absolute inset-0 bg-cover bg-center" style={{ backgroundImage: "url('/bg-mountains.jpg')", height: '120%', top: '-10%' }} />
<div className="mid-layer absolute inset-0 flex items-center justify-center">
<h2 className="text-6xl font-bold text-white">Заголовок</h2>
</div>
<div className="fg-layer absolute bottom-0 w-full">
<svg viewBox="0 0 1440 200">{/* облака, деревья */}</svg>
</div>
</div>
)
} השוואת ביצועים במובייל
| שיטה | קצב פריימים ב-iOS (iPhone 12) | עומס GPU | תמיכה ב-iOS Safari |
|---|---|---|---|
| CSS (background-attachment) | 0 (לא עובד) | — | לא |
| JavaScript (requestAnimationFrame) | 30–45 | 70% | כן, אך דורש התאמות |
| GSAP ScrollTrigger | 55–60 | 40% | כן, אופטימיזציה מובנית |
GSAP ScrollTrigger מציג את התוצאות הטובות ביותר הודות לשימוש ב-will-change וניהול אוטומטי של requestAnimationFrame.
כיצד ליישם פרלקסה ב-JavaScript טהור
אם הפרויקט שלך אינו משתמש ב-GSAP, קיימת גישה מוכחת עם JS טהור ו-requestAnimationFrame. הכללים העיקריים: קרא את scrollY רק באירוע גלילה (passive), כתוב ל-DOM דרך requestAnimationFrame, השתמש ב-transform במקום top/left וב-will-change: transform. הנה hook עובד של React:
// hooks/useParallax.ts
import { useEffect, useRef, useState } from 'react'
interface ParallaxOptions {
speed?: number // 0 = не двигается, 1 = вместе со скроллом, -1 = обратно
direction?: 'vertical' | 'horizontal'
disabled?: boolean // для мобильных
}
export function useParallax({
speed = 0.5,
direction = 'vertical',
disabled = false,
}: ParallaxOptions = {}) {
const elementRef = useRef<HTMLElement>(null)
const rafRef = useRef<number | null>(null)
const lastScrollY = useRef(0)
useEffect(() => {
if (disabled || !elementRef.current) return
const el = elementRef.current
el.style.willChange = 'transform'
let ticking = false
const updateTransform = () => {
const rect = el.getBoundingClientRect()
const viewportCenter = window.innerHeight / 2
const elementCenter = rect.top + rect.height / 2
const distanceFromCenter = elementCenter - viewportCenter
const offset = -distanceFromCenter * (speed - 1)
if (direction === 'vertical') {
el.style.transform = `translateY(${offset}px)`
} else {
el.style.transform = `translateX(${offset}px)`
}
ticking = false
}
const onScroll = () => {
if (!ticking) {
rafRef.current = requestAnimationFrame(updateTransform)
ticking = true
}
}
window.addEventListener('scroll', onScroll, { passive: true })
updateTransform() // начальное положение
return () => {
window.removeEventListener('scroll', onScroll)
if (rafRef.current) cancelAnimationFrame(rafRef.current)
el.style.willChange = ''
el.style.transform = ''
}
}, [speed, direction, disabled])
return elementRef
} // components/ParallaxImage.tsx
import { useParallax } from '../hooks/useParallax'
interface ParallaxImageProps {
src: string
alt: string
speed?: number
className?: string
}
export function ParallaxImage({ src, alt, speed = 0.6, className }: ParallaxImageProps) {
// Отключаем параллакс на мобильных — экономим ресурсы
const isMobile = typeof window !== 'undefined' ? window.matchMedia('(max-width: 768px)').matches : false
const ref = useParallax({ speed, disabled: isMobile })
return (
<div className="overflow-hidden">
<img
ref={ref as React.RefObject<HTMLImageElement>}
src={src}
alt={alt}
className={className}
// Немного увеличиваем изображение чтобы скрыть края при параллаксе
style={{ transform: 'scale(1.1)', transformOrigin: 'center' }}
/>
</div>
)
} מדוע לשקול prefers-reduced-motion?
משתמשים עם הפרעות וסטיבולריות עלולים לחוות אי נוחות מאנימציה. הוסף תמיכה ב-prefers-reduced-motion כדי להשבית פרלקסה במקרים כאלה. דוגמת hook:
// hooks/useReducedMotion.ts
import { useEffect, useState } from 'react'
export function useReducedMotion(): boolean {
const [reducedMotion, setReducedMotion] = useState(false)
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
setReducedMotion(mq.matches)
const handler = (e: MediaQueryListEvent) => setReducedMotion(e.matches)
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
}, [])
return reducedMotion
}טעויות אופייניות ביישום פרלקסה:
- שימוש באירוע גלילה ללא requestAnimationFrame — מוביל לנפילות פריימים.
- החלת background-attachment: fixed ב-iOS — לא עובד, יש צורך ב-JS.
- אין fallback למכשירים חלשים — מכשירים ניידים מתחממים ונתקעים.
- התעלמות מ-prefers-reduced-motion — הפרת נגישות.
אנו נמנעים מטעויות אלה הודות לרשימת בדיקה של 20 נקודות ובדיקות רגרסיה במכשירים אמיתיים.
מה כלול בעבודה
- ניתוח מוקאפים ובחירת אסטרטגיית פרלקסה (CSS/JS/GSAP)
- פיתוח רכיבי React מותאמים אישית עם אופטימיזציית ביצועים
- בדיקות ב-iOS, Android, דפדפני שולחן עבודה
- שילוב prefers-reduced-motion לנגישות
- תיעוד קוד והעברת גישה
- 3 חודשי תמיכה לאחר המסירה
צור קשר כדי לדון בפרטים.
תהליך העבודה
- אנליטיקה — הגדרת מטרות הפרויקט ואילוצים טכניים
- עיצוב — בחירת מחסנית (React + GSAP או hook מותאם)
- יישום — קוד עם הערות ובדיקות יחידה
- בדיקות — אימות ב-iOS/Android/Chrome/Firefox/Safari
- פריסה — אירוח בשרת שלך או ב-Vercel
לוחות זמנים: בין 2 ל-5 ימי עבודה בהתאם למורכבות. צור קשר להערכה חינמית של הפרויקט שלך. הזמן פרלקסה לאתר שלך — קבל ייעוץ מהנדס.
אנו ממליצים לבדוק את תיעוד MDN: will-change להאצת GPU.







