Parallax Scrolling Backgrounds

Create depth and immersive experiences with parallax effects

Understanding Parallax Scrolling

Parallax scrolling creates an illusion of depth by moving background images slower than foreground content during scrolling. This technique adds visual interest and creates immersive user experiences. Modern CSS and JavaScript offer multiple approaches to achieve parallax effects.

Types of Parallax Effects

  • CSS Fixed Attachment: Simple, performant, limited control
  • Transform-based: Smooth, GPU-accelerated, more flexible
  • JavaScript-driven: Complete control, custom animations
  • Intersection Observer: Performance-optimized, modern approach

CSS Fixed Attachment Method

The simplest parallax effect uses background-attachment: fixed. This creates a stationary background while content scrolls over it.

/* Basic fixed parallax */
.parallax-section {
    height: 100vh;
    background-image: url('mountain-landscape.jpg');
    background-attachment: fixed;
    background-position: center;
    background-repeat: no-repeat;
    background-size: cover;
}

/* Content overlay */
.parallax-content {
    background: rgba(0,0,0,0.5);
    color: white;
    padding: 3rem;
    text-align: center;
}

Fixed Attachment Demo

Scroll within this container to see the fixed background effect.

'); ">

Mountain Parallax Effect

This background stays fixed while you scroll, creating a window effect.

Multi-Layer Parallax with Transform

Create more sophisticated parallax effects by animating multiple layers at different speeds using CSS transforms and JavaScript.

Layered Mountains

Multiple layers moving at different speeds

/* HTML Structure */
<div class="parallax-container">
    <div class="layer" data-speed="0.2"></div>
    <div class="layer" data-speed="0.5"></div>
    <div class="layer" data-speed="0.8"></div>
    <div class="content">Foreground Content</div>
</div>

/* CSS */
.parallax-container {
    height: 100vh;
    overflow: hidden;
    position: relative;
}

.layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 120%;
    will-change: transform;
}

/* JavaScript */
window.addEventListener('scroll', () => {
    const scrolled = window.pageYOffset;
    const layers = document.querySelectorAll('.layer');
    
    layers.forEach(layer => {
        const speed = layer.dataset.speed;
        const yPos = -(scrolled * speed);
        layer.style.transform = `translateY(${yPos}px)`;
    });
});

JavaScript Parallax Techniques

1. Transform-Based Parallax

Uses CSS transforms for smooth, GPU-accelerated animations.

function updateParallax() {
    const scrolled = window.pageYOffset;
    
    elements.forEach((el, index) => {
        const speed = (index + 1) * 0.5;
        const yPos = scrolled * speed;
        el.style.transform = `translateY(${yPos}px)`;
    });
}

window.addEventListener('scroll', updateParallax);

2. Intersection Observer

Performance-optimized parallax that only animates visible elements.

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            // Start parallax animation
            animateParallax(entry.target);
        } else {
            // Stop animation for performance
            stopParallax(entry.target);
        }
    });
});

// Observe parallax elements
document.querySelectorAll('.parallax').forEach(el => {
    observer.observe(el);
});

3. RequestAnimationFrame

Smooth animations using the browser's optimal rendering cycle.

let ticking = false;

function updateParallax() {
    const scrolled = window.pageYOffset;
    
    // Update parallax elements
    parallaxElements.forEach(el => {
        const speed = el.dataset.speed || 0.5;
        const yPos = scrolled * speed;
        el.style.transform = `translateY(${yPos}px)`;
    });
    
    ticking = false;
}

function requestTick() {
    if (!ticking) {
        requestAnimationFrame(updateParallax);
        ticking = true;
    }
}

window.addEventListener('scroll', requestTick);

Advanced Parallax Effects

1. 3D Perspective Parallax

/* 3D parallax container */
.parallax-3d {
    height: 100vh;
    overflow-x: hidden;
    overflow-y: auto;
    perspective: 1px;
}

.parallax-3d .layer {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}

/* Different depths */
.layer-back {
    transform: translateZ(-1px) scale(2);
}

.layer-middle {
    transform: translateZ(-0.5px) scale(1.5);
}

.layer-front {
    transform: translateZ(0);
}

2. Mouse-Following Parallax

Mouse Parallax

Move your mouse to see the effect

// Mouse parallax effect
document.addEventListener('mousemove', (e) => {
    const { clientX, clientY } = e;
    const centerX = window.innerWidth / 2;
    const centerY = window.innerHeight / 2;
    
    const deltaX = (clientX - centerX) / centerX;
    const deltaY = (clientY - centerY) / centerY;
    
    elements.forEach((el, index) => {
        const depth = (index + 1) * 10;
        const rotateX = deltaY * depth;
        const rotateY = deltaX * depth;
        
        el.style.transform = 
            `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
    });
});

3. Scroll-Based Animations

// Scroll-triggered parallax with easing
function easeOutQuart(t) {
    return 1 - (--t) * t * t * t;
}

function animateOnScroll() {
    const scrolled = window.pageYOffset;
    const windowHeight = window.innerHeight;
    
    parallaxElements.forEach(el => {
        const elementTop = el.offsetTop;
        const elementHeight = el.offsetHeight;
        
        // Check if element is in viewport
        if (scrolled + windowHeight > elementTop && 
            scrolled < elementTop + elementHeight) {
            
            const progress = 
                (scrolled + windowHeight - elementTop) / 
                (windowHeight + elementHeight);
            
            const easedProgress = easeOutQuart(progress);
            const translateY = easedProgress * 100;
            
            el.style.transform = `translateY(${translateY}px)`;
        }
    });
}

Performance Optimization

Critical Performance Tips

1. Use GPU Acceleration

/* Force GPU acceleration */
.parallax-element {
    will-change: transform;
    transform: translateZ(0); /* Hack for older browsers */
    backface-visibility: hidden;
}

/* Use transforms instead of changing position */
/* Good */
element.style.transform = `translateY(${yPos}px)`;

/* Bad - causes reflow */
element.style.top = yPos + 'px';

2. Throttle Scroll Events

// Throttle function
function throttle(func, limit) {
    let inThrottle;
    return function() {
        const args = arguments;
        const context = this;
        if (!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    }
}

// Throttled scroll handler
const throttledParallax = throttle(updateParallax, 16); // ~60fps
window.addEventListener('scroll', throttledParallax);

3. Passive Event Listeners

// Passive listeners for better performance
window.addEventListener('scroll', updateParallax, { 
    passive: true 
});

// Use Intersection Observer for viewport detection
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            entry.target.classList.add('animate');
        }
    });
}, { 
    threshold: 0.1,
    rootMargin: '50px'
});

4. Reduce Paint and Layout

/* Minimize expensive properties */
.parallax-element {
    /* Avoid these - cause repaint/reflow */
    /* width, height, padding, margin, border */
    
    /* Use these instead */
    transform: translateY(0);
    opacity: 1;
    filter: blur(0);
}

/* Optimize background images */
.parallax-bg {
    background-size: cover;
    background-attachment: fixed;
    /* Use image-rendering for performance */
    image-rendering: optimizeSpeed;
}

Mobile Optimization

Mobile Parallax Challenges

  • iOS Safari: background-attachment: fixed is disabled
  • Performance: Mobile devices have limited processing power
  • Battery: Complex animations drain battery quickly
  • Touch scrolling: Different scroll behavior than desktop
/* Mobile-first parallax approach */
.parallax-section {
    /* Default: simple background for mobile */
    background-attachment: scroll;
    background-position: center;
    background-size: cover;
}

/* Enhanced parallax for capable devices */
@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
    .parallax-section {
        background-attachment: fixed;
    }
}

/* JavaScript feature detection */
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
    .test(navigator.userAgent);

const prefersReducedMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
).matches;

if (!isMobile && !prefersReducedMotion) {
    // Enable full parallax
    initParallax();
} else {
    // Use simple scroll effects
    initSimpleEffects();
}

Alternative Mobile Techniques

/* Transform-based mobile parallax */
.mobile-parallax {
    transform: translateY(0);
    transition: transform 0.1s ease-out;
}

/* Simplified scroll effects */
function mobileParallax() {
    const scrolled = window.pageYOffset;
    const elements = document.querySelectorAll('.mobile-parallax');
    
    elements.forEach(el => {
        const rect = el.getBoundingClientRect();
        if (rect.top < window.innerHeight && rect.bottom > 0) {
            const speed = 0.1; // Reduced speed for mobile
            const yPos = scrolled * speed;
            el.style.transform = `translateY(${yPos}px)`;
        }
    });
}

// Reduced frequency for mobile
if (window.innerWidth < 768) {
    window.addEventListener('scroll', 
        throttle(mobileParallax, 32), // 30fps for mobile
        { passive: true }
    );
}

CSS-Only Parallax Techniques

1. Pure CSS Transform Parallax

/* CSS-only parallax using perspective */
.parallax-container {
    height: 100vh;
    overflow-x: hidden;
    overflow-y: auto;
    perspective: 1px;
}

.parallax-layer {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}

.parallax-back {
    transform: translateZ(-1px) scale(2);
}

.parallax-mid {
    transform: translateZ(-0.5px) scale(1.5);
}

.parallax-front {
    transform: translateZ(0);
}

2. CSS Animation Parallax

/* Keyframe-based parallax */
@keyframes parallaxMove {
    0% { transform: translateY(0); }
    100% { transform: translateY(-50px); }
}

.css-parallax {
    animation: parallaxMove 10s ease-in-out infinite alternate;
    animation-play-state: paused;
    animation-delay: calc(var(--scroll) * -1s);
    animation-iteration-count: 1;
    animation-fill-mode: both;
}

/* Update CSS custom property with JavaScript */
window.addEventListener('scroll', () => {
    const scrolled = window.pageYOffset;
    const maxScroll = document.body.scrollHeight - window.innerHeight;
    const scrollPercent = scrolled / maxScroll;
    
    document.documentElement.style.setProperty('--scroll', scrollPercent);
});

Accessibility Considerations

Accessibility Best Practices

  • Respect prefers-reduced-motion: Disable parallax for sensitive users
  • Provide alternatives: Ensure content is accessible without parallax
  • Avoid vestibular triggers: Limit extreme motion effects
  • Test with screen readers: Ensure compatibility
/* Respect user preferences */
@media (prefers-reduced-motion: reduce) {
    .parallax-element {
        animation: none;
        transform: none !important;
    }
    
    .parallax-section {
        background-attachment: scroll;
    }
}

/* JavaScript implementation */
const prefersReducedMotion = window.matchMedia(
    '(prefers-reduced-motion: reduce)'
);

function initParallax() {
    if (prefersReducedMotion.matches) {
        // Provide static alternative
        return;
    }
    
    // Initialize parallax effects
    setupParallaxEffects();
}

// Listen for preference changes
prefersReducedMotion.addEventListener('change', () => {
    if (prefersReducedMotion.matches) {
        disableParallax();
    } else {
        enableParallax();
    }
});

Popular Parallax Libraries

1. AOS (Animate On Scroll)

<!-- Include AOS -->
<link rel="stylesheet" href="https://unpkg.com/aos@next/dist/aos.css" />

<!-- HTML -->
<div data-aos="fade-up" data-aos-duration="1000">
    Content with parallax effect
</div>

<script src="https://unpkg.com/aos@next/dist/aos.js"></script>
<script>
    AOS.init({
        duration: 1200,
        once: true,
        offset: 100
    });
</script>

2. Rellax.js

<!-- HTML -->
<div class="rellax" data-rellax-speed="-7">
    Slow parallax element
</div>

<div class="rellax" data-rellax-speed="5">
    Fast parallax element
</div>

<script src="https://cdn.jsdelivr.net/gh/dixonandmoe/rellax@master/rellax.min.js"></script>
<script>
    var rellax = new Rellax('.rellax', {
        speed: -7,
        center: false,
        wrapper: null,
        round: true,
        vertical: true,
        horizontal: false
    });
</script>

3. Lax.js

<script src="https://cdn.jsdelivr.net/npm/lax.js"></script>

<div class="lax" data-lax-preset="spin">Spinning element</div>
<div class="lax" data-lax-preset="fadeInOut">Fading element</div>

<script>
    window.onload = function() {
        lax.setup(); // Initialize
        
        const updateLax = () => {
            lax.update(window.scrollY);
            window.requestAnimationFrame(updateLax);
        }
        
        window.requestAnimationFrame(updateLax);
    }
</script>

Related Resources