Parallax Scrolling Effect Implementation: GSAP, CSS, JavaScript

Static websites lose user attention, and poor-quality parallax only repels them with lag and janky animation. We develop smooth parallax effects in line with modern performance and accessibility standards. Our team delivers the project turnkey—from technique selection to final optimization—ensuring stable performance on any device and ongoing support.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113
  • Website development for SBH Partners
    Website development for SBH Partners
    1201
  • Website development for Red Pear
    Website development for Red Pear
    619

Parallax Scrolling Effect Implementation: GSAP, CSS, JavaScript

Parallax — different speed of layer movement during scrolling, creating an illusion of depth. In practice, we often encounter lag: on iOS, animation stutters, on weak devices FPS drops to 15. We solve this problem — using modern methods with Core Web Vitals and accessibility in mind. Our team with 5 years of experience and 50+ projects with parallax guarantees smooth animation on any device, reducing development time by 40% compared to custom solutions. The cost is calculated individually, depending on the complexity and number of layers.

How Parallax Affects Core Web Vitals

Parallax directly impacts LCP and CLS metrics. If the implementation is not optimal, the browser has to repaint large areas, increasing load time and layout shifts. According to web.dev, improper use of will-change can lead to FPS loss. Proper parallax optimization allows reducing the animation budget by 30% and improving LCP by 0.5s.

How to Choose a Parallax Method for a Specific Task

Parallax is implemented in three ways: CSS (background-attachment: fixed) — limited, JavaScript (requestAnimationFrame) — universal, or GSAP ScrollTrigger scrub — maximum smoothness. To understand the difference, let's look at a comparison:

Criterion CSS (background-attachment) JavaScript (requestAnimationFrame) GSAP ScrollTrigger scrub
Performance High (GPU) on desktop, zero on iOS Medium, drops on weak devices High, built-in optimization
Cross-browser Breaks on iOS Safari Works everywhere, but requires testing Works everywhere, including mobile
Setup complexity Minimal High (synchronizing scroll and rAF) Medium (scrub configuration)
Capabilities Only background, single layer Any number of layers, direction Multi-layer, scrub with delay
prefers-reduced-motion support No Must be written manually Built into GSAP

GSAP ScrollTrigger scrub is the recommended approach. It provides 3–5 times better smoothness on weak devices compared to custom solutions, thanks to built-in requestAnimationFrame and scrub management.

Why GSAP Is Better Than Custom Solutions

GSAP handles parallax via scrub — it ties animation to scroll position with optional smoothing. The built-in ScrollTrigger plugin solves synchronization and performance issues. Example implementation:

// 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>
  )
}

Mobile Performance Comparison

Method FPS on iOS (iPhone 12) GPU Load iOS Safari Support
CSS (background-attachment) 0 (does not work) — No
JavaScript (requestAnimationFrame) 30–45 70% Yes, but requires tweaks
GSAP ScrollTrigger 55–60 40% Yes, built-in optimization

GSAP ScrollTrigger shows the best results thanks to will-change usage and automatic requestAnimationFrame management.

How to Implement Parallax in Pure JavaScript

If your project does not use GSAP, there is a proven approach with pure JS and requestAnimationFrame. The main rules: read scrollY only in scroll event (passive), write to DOM via requestAnimationFrame, use transform instead of top/left and will-change: transform. Here is a working React hook:

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

interface ParallaxOptions {
  speed?: number // 0 = does not move, 1 = moves with scroll, -1 = opposite
  direction?: 'vertical' | 'horizontal'
  disabled?: boolean // for mobile
}

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() // initial position

    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>
  )
}

Why Consider prefers-reduced-motion?

Users with vestibular disorders may experience discomfort from animation. Add prefers-reduced-motion support to disable parallax in such cases. Example 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
}

Typical mistakes in parallax implementation:

  • Using scroll event without requestAnimationFrame — leads to frame drops.
  • Applying background-attachment: fixed on iOS — does not work, need JS.
  • No fallback for weak devices — mobiles overheat and lag.
  • Ignoring prefers-reduced-motion — accessibility violation.

We avoid these mistakes thanks to a 20-point checklist and regression tests on real devices.

What’s Included in the Work

  • Analysis of mockups and selection of parallax strategy (CSS/JS/GSAP)
  • Development of custom React components with performance optimization
  • Testing on iOS, Android, desktop browsers
  • Integration of prefers-reduced-motion for accessibility
  • Code documentation and access transfer
  • 3 months of support after delivery

Contact us to discuss details.

Work Process

  1. Analytics — define project goals and technical constraints
  2. Design — select stack (React + GSAP or custom hook)
  3. Implementation — code with comments and unit tests
  4. Testing — verification on iOS/Android/Chrome/Firefox/Safari
  5. Deployment — hosting on your server or Vercel

Timelines: from 2 to 5 working days depending on complexity. Contact us for an evaluation of your project. Order parallax for your website — get an engineer consultation.

We recommend checking the MDN documentation: will-change for GPU acceleration.