Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      The Power Of The Intl API: A Definitive Guide To Browser-Native Internationalization

      August 8, 2025

      This week in AI dev tools: GPT-5, Claude Opus 4.1, and more (August 8, 2025)

      August 8, 2025

      Elastic simplifies log analytics for SREs and developers with launch of Log Essentials

      August 7, 2025

      OpenAI launches GPT-5

      August 7, 2025

      5 ways business leaders can transform workplace culture – and it starts by listening

      August 8, 2025

      My 4 favorite image editing apps on Linux – and two are free Photoshop alternatives

      August 8, 2025

      How Google’s Genie 3 could change AI video – and let you build your own interactive worlds

      August 8, 2025

      How you’re charging your tablet is slowly killing it – 3 methods to avoid (and the right way)

      August 8, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Establishing Consistent Data Foundations with Laravel’s Database Population System

      August 8, 2025
      Recent

      Establishing Consistent Data Foundations with Laravel’s Database Population System

      August 8, 2025

      Generate Postman Collections from Laravel Routes

      August 8, 2025

      This Week in Laravel: Free Laravel Idea, Laracon News, and More

      August 8, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Lenovo Legion Go 2 vs Legion Go — How Do These Gaming Handhelds Compare Based on Rumored Specs?

      August 8, 2025
      Recent

      Lenovo Legion Go 2 vs Legion Go — How Do These Gaming Handhelds Compare Based on Rumored Specs?

      August 8, 2025

      9 Default Settings in Windows 11 You Didn’t Know Could Affect Performance and Privacy

      August 8, 2025

      DICE Responds to Battlefield 6 Community: Key Updates on Map Flow and Class Mechanics

      August 8, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»News & Updates»Bringing Back Parallax With Scroll-Driven CSS Animations

    Bringing Back Parallax With Scroll-Driven CSS Animations

    August 6, 2025

    For a period in the 2010s, parallax was a guaranteed way to make your website “cool”. Indeed, Chris Coyier was writing about it as far back as 2008.

    For those unfamiliar with the concept, parallax is a pattern in which different elements of a webpage move at varying speeds as the user scrolls, creating a three-dimensional, layered appearance. A true parallax effect was once only achievable using JavaScript. However, scroll-driven animations have now given us a CSS-only solution, which is free from the main-thread blocking that can plague JavaScript animations.

    Parallax may have become a little cliché, but I think it’s worth revisiting with this new CSS feature.

    Note: Scroll-driven animations are available on Chrome, Edge, Opera, and Firefox (behind a feature flag) at the time of writing. Use a supported browser when following this tutorial.

    Starting code

    In this example, we will apply parallax animations to the background and icons within the three “hero” sections of a universe-themed webpage. We’ll start with some lightly styled markup featuring alternating hero and text sections while including some space-related nonsense as placeholder content.

    CodePen Embed Fallback

    Adding initial animations

    Let’s add an animation to the background pattern within each hero section to modify the background position.

    @keyframes parallax {
      from {
        background-position: bottom 0px center;
      }
      to {
        background-position: bottom -400px center;
      }
    }
    
    section.hero {
      /* previous code */
    + animation: parallax 3s linear;
    }

    Here we use the keyframes CSS rule to create a start and end position for the background. Then we attach this animation to each of our hero sections using the animation property.

    By default, CSS animations are duration-based and run when the specified selector is loaded in the DOM. If you refresh your browser, you will see the animation running for three seconds as soon as the page loads.

    We do not want our animation to be triggered immediately. Instead, we intend to use the page’s scroll position as a reference to calculate the animation’s progress.

    Scroll-driven animations provide two new animation timeline CSS functions. These additions, view() and scroll(), tell the browser what to reference when calculating the progress of a CSS animation. We will use the view() function later, but for now, let’s focus on scroll(). The scroll progress timeline couples the progression of an animation to the user’s scroll position within a scroll container. Parameters can be included to change the scroll axis and container element, but these are not necessary for our implementation.

    Let’s use a scroll progress timeline for our animation:

    section.hero {
      /* previous code */
    - animation: parallax 3s linear;
    + animation: parallax linear;
    + animation-timeline: scroll(); 
    }

    If you refresh the page, you will notice that as you scroll down, the position of the background of each hero section also changes. If you scroll back up, the animation reverses. As a bonus, this CSS animation is handled off the main thread and thus is not subject to blocking by any JavaScript that may be running.

    Using the view progress timeline

    Now let’s add a new parallax layer by animating the header text and icons within each hero section. This way, the background patterns, headers, and main page content will all appear to scroll at different speeds. We will initially use the scroll() CSS function for the animation timeline here as well.

    @keyframes float {
      from {
        top: 25%;
      }
      to {
        top: 50%;
      }
    }
    
    .hero-content {
      /* previous code */
    + position: absolute;
    + top: 25%;
    + animation: float linear;
    + animation-timeline: scroll(); 
    }

    That’s not quite right. The animation for the sections further down the page is nearly done by the time they come into view. Luckily, the view animation timeline solves this problem. By setting the animation-timeline property to view(), our animation progresses based on the position of the subject within the scrollport, which is the part of the container that is visible when scrolling. Like the scroll animation timeline, scrolling in reverse will also reverse the animation.

    Let’s try changing our animation timeline property for the hero text:

    .hero-content {
      /* previous code */
    - animation-timeline: scroll(); 
    + animation-timeline: view(); 
    }

    That looks pretty good, but there is a problem with the header content flashing into the view when scrolling back up the document. This is because the view timeline is calculated based on the original, pre-animation positioning of the subject element.

    We can solve this by adding an inset parameter to the view() function. This adjusts the size of the container in which the animation will take place. According to MDN’s documentation, the “inset is used to determine whether the element is in view which determines the length of the animation timeline. In other words, the animation lasts as long as the element is in the inset-adjusted view.”

    So, by using a negative value, we make the container larger than the window and trigger the animation to start a little before and end a little after the subject is visible. This accounts for the fact that the subject moves during the animation.

    - animation-timeline: view();
    + animation-timeline: view(-100px);

    Now both the text and background animate smoothly at different speeds.

    CodePen Embed Fallback

    Adjusting animations using animation ranges

    So far, we have employed both scroll and view progress timelines. Let’s look at another way to adjust the start and end timing of the animations using the animation-range property. It can be used to modify where along the timeline the animation will start and end.

    We’ll start by adding a view() timeline animation to the #spaceship emoji:

    @keyframes launch {
      from {
        transform: translate(-100px, 200px);
      }
      to {
        transform: translate(100px, -100px);
      }
    }
    
    #spaceship {
      animation: launch;
      animation-timeline: view();
    }

    Again, we see the emoji returning to its 0% position once its original unanimated position is outside of the scrollport.

    As discussed before, animations are based on the original pre-animation position of the subject. Previously, we solved this by adding an inset parameter to the view() function. We can also adjust the animation range and tell our animation to continue beyond 100% of the animation timeline without having to manipulate the inset of the view timeline itself.

    #spaceship {
      animation: launch;
      animation-timeline: view();
    + animation-range: 0% 120%;
    }

    Now the animation continues until we have scrolled an extra 20% beyond the calculated scroll timeline’s normal endpoint.

    Let’s say that we want to add an animation to the #comet emoji, but we don’t want it to start animating until it has passed 4rem from the bottom of the scrollport:

    @keyframes rotate {
      from {
        transform: rotate(0deg) translateX(100px);
      }
      to {
        transform: rotate(-70deg) translateX(0px);
      }
    }
    
    #comet {
      animation: rotate linear;
      transform-origin: center 125px;
      animation-timeline: view();
      animation-range: 4rem 120%;
    }

    Here we see the “delayed” animation in action:

    We can also combine animation ranges to run completely different animations at different points within the same timeline! Let’s illustrate this by combining animation ranges for the #satellite icon at the top of the page. The result is that the first animation runs until the icon passes 80% of the scrollport, then the second animation takes over for the final 20%.

    @keyframes orbit-in {
      0% {
        transform: rotate(200deg);
      }
      100% {
        transform: rotate(0deg);
      }
    }
    
    @keyframes orbit-out {
      0% {
        transform: translate(0px, 0px);
      }
      100% {
        transform: translate(-50px, -15px);
      }
    }
    
    #satellite {
      animation: orbit-in linear, orbit-out ease;
      animation-timeline: view();
      animation-range: 0% 80%, 80% 110%;
    }

    Fallbacks and accessibility

    Our webpage features numerous moving elements that may cause discomfort for some users. Let’s consider accessibility for motion sensitivities and incorporate the prefers-reduced-motion CSS media feature.

    There are two possible values: no-preference, and reduce. If we want to fine-tune the webpage with animations disabled by default and then enhance each selector with animations and associated styles, then we can use no-preference to enable them.

    @media (prefers-reduced-motion: no-preference) {
      .my-selector {
        position: relative;
        top: 25%;
        animation: cool-animation linear;
        animation-timeline: scroll(); 
      }
    }

    For us, however, the webpage content and images will still all be visible if we disable all animations simultaneously. This can be done concisely using the reduce option. It’s important to note that this sort of blanket approach works for our situation, but you should always consider the impact on your specific users when implementing accessibility features.

    @media (prefers-reduced-motion: reduce) {
      .my-selector {
        animation: none !important;
      }
    }

    In addition to considering accessibility, we should also take into account that scroll-driven animations are not supported by all browsers at the time of writing. If we care a lot about users seeing our animations, we can add a polyfill (direct link) to extend this functionality to currently unsupported browsers. This, however, will force the animation to run on the main thread.

    Alternatively, we could decide that performance is important enough to skip the animations on unsupported browsers, thereby keeping the main thread clear. In this case, we can use the @supports selector and include the styles only on supported browsers.

    Here is the final code with everything, including the polyfill and reduced motion fallback:

    CodePen Embed Fallback

    Conclusion

    There we go, we just re-created a classic web effect with scroll-driven animations using scroll and view progress timelines. We also discussed some of the parameters that can be used to adjust animation behavior. Whether or not parallax is your thing, I like the idea that we can use a modern approach that is capable of doing what we could before… only better with a dash of progressive enhancement.

    More information

    • Unleash the Power of Scroll-Driven Animations
    • animation-timeline (CSS-Tricks)
    • CSS scroll-driven animations (MDN)
    • Scroll-driven Animations Demo Site (Bramus)

    Bringing Back Parallax With Scroll-Driven CSS Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleBuilding Aether 1: Sound Without Boundaries
    Next Article Firefly AIBOX-3588S Running Linux: Access Remote File Systems Over SSH with SSHFS

    Related Posts

    News & Updates

    5 ways business leaders can transform workplace culture – and it starts by listening

    August 8, 2025
    News & Updates

    My 4 favorite image editing apps on Linux – and two are free Photoshop alternatives

    August 8, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Raspberry Pi OS – Debian-based distro

    Linux

    Safeguarding your browsing history | Kaspersky official blog

    Security

    CVE-2025-43926 – Znuny Unauthenticated User Preference Injection Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    DOOM: The Dark Ages had the lowest Steam launch numbers in series history — Is it suffering from the ‘Game Pass Effect’?

    News & Updates

    Highlights

    Machine Learning

    Combine keyword and semantic search for text and images using Amazon Bedrock and Amazon OpenSearch Service

    April 24, 2025

    Customers today expect to find products quickly and efficiently through intuitive search functionality. A seamless…

    CVE-2024-58251 – BusyBox Netstat Terminal Escape Sequence Injection Denial of Service

    April 23, 2025

    Raspberry Pi 5 Desktop Mini PC

    June 14, 2025

    Ukraine claims to have hacked secrets from Russia’s newest nuclear submarine

    August 6, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.