Scroll-triggered animation

Matt Perry

In this tutorial, we're going to build the Scroll-triggered animation example step-by-step.

This tutorial is rated beginner difficulty, which means we'll spend some time explaining the Motion APIs that we've chosen to use (and why), and also any browser APIs we encounter that might be unfamiliar to beginners.

Here's a live demo of the example we're going to be creating:

Loading...

Introduction

The Scroll-triggered animation example shows how to create animations that trigger when elements enter the viewport as users scroll. This is a common pattern for adding polish to landing pages and marketing sites.

This example uses two Motion functions: inView for detecting when elements enter the viewport, and animate for creating smooth animations.

Get started

Let's begin with a simple layout containing four full-screen sections. Each section will display a single word that we'll animate when it scrolls into view.

<div class="example">
    <section class="scroll-section"><pre>Scroll</pre></section>
    <section class="scroll-section"><pre>to</pre></section>
    <section class="scroll-section"><pre>trigger</pre></section>
    <section class="scroll-section"><pre>animations!</pre></section>
</div>

<style>
    /** Copy styles from example source code */
<

Notice that we've set the pre elements to start invisible (opacity: 0) and positioned off-screen (translateX(-100px)). This ensures they're hidden until we animate them in.

Let's animate!

Import from Motion

First, import the inView and animate functions from Motion:

<script type="module">
    import { animate, inView } from "motion"
</script>

Detect when elements enter the viewport

The inView function lets us run code whenever elements scroll into view. It accepts a selector string and a callback function that runs when matching elements become visible:

<script type="module">
    import { animate, inView } from "motion"

    inView(".scroll-section pre", (element) => {
        // This callback runs when each <pre> element enters the viewport
    })
</script>

Motion will automatically detect all elements matching the selector .scroll-section pre and call our function whenever they enter the viewport. The element parameter gives us a reference to the specific element that became visible.

Animate elements as they appear

Inside the inView callback, we can use the animate function to animate the element. The animate function accepts three arguments: the element to animate, the properties to animate, and options for controlling the animation:

<script type="module">
    import { animate, inView } from "motion"

    inView(".scroll-section pre", (element) => {
        animate(
            element,
            { opacity: 1, x: [-100, 0] },
            {
                duration: 0.9,
                easing: [0.17, 0.55, 0.55, 1],
            }
        )
    })
</script>

Let's break down what's happening:

  • opacity: 1 animates the element from its current opacity (0 from our CSS) to fully visible

  • x: [-100, 0] animates the element's horizontal position from -100px to 0, creating a slide-in effect from the left

  • duration: 0.9 makes the animation last 0.9 seconds

  • easing: [0.17, 0.55, 0.55, 1] defines a cubic bezier curve for smooth, natural motion

When you use an array like x: [-100, 0], Motion animates from the first value to the second. This is different from writing x: 0, which would animate from the element's current x position (set by our CSS transform) to 0.

Animate elements as they leave

The inView callback can return a cleanup function that runs when the element scrolls out of view. This lets us reverse the animation when users scroll back up:

<script type="module">
    import { animate, inView } from "motion"

    inView(".scroll-section pre", (element) => {
        animate(
            element,
            { opacity: 1, x: [-100, 0] },
            {
                duration: 0.9,
                easing: [0.17, 0.55, 0.55, 1],
            }
        )

        return () => animate(element, { opacity: 0, x: -100 })
    })
</script>

Now when an element leaves the viewport, Motion calls our cleanup function, which animates the element back to its hidden state. This creates a seamless scroll experience where elements fade in as they enter and fade out as they leave.

Conclusion

We've built a scroll-triggered animation that brings elements to life as users scroll through the page. By combining inView to detect visibility and animate to create smooth transitions, we created an engaging scroll experience with just a few lines of code. Motion makes it easy to add this kind of polish to any website without complex setup or manual scroll listeners.

Motion is supported by the best in the industry.