Skip to content
GitHub Linkedin

Animate on scroll with Svelte Inview - Little Bits

Little Bits is a series of short articles explaining quick solutions to common dev problems. No unnecessary descriptions or code snippets. No bullshit.

In this episode, I want to show you how you can animate elements in your Svelte app on scroll.

Problem

When user is scrolling your Svelte app/website, we want to animate the entering elements to get that extra fancy feel.

Gif showing the scroll animation

The best solution is to use Intersection Observer, but we can also use a nice abstraction built on top of it - svelte-inview. This is a small library I created some time ago that greatly simplify usage of the IO API. Let's see how we can do this.

Solution

Create an app with Vite

Let's use Vite to create a simple Svelte App:

npm create vite@latest my-svelte-app -- --template svelte

cd my-svelte-app

npm i

npm run dev

Add scaffolding

Let's add some simple app with basic styling. Paste that code in your App.svelte file:

<script>
  import AnimatedElement from './lib/AnimatedElement.svelte';
</script>

<main>
  <div class="hero">
    <h1>Hello Svelte Inview</h1>
    <p>Scroll down to animate the elements</p>
  </div>
  <div class="full-height">
    <AnimatedElement />
  </div>
  <div class="full-height">
    <AnimatedElement />
  </div>
</main>

<style>
  :root {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
      Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  }

  main {
    text-align: center;
    padding: 0 1em;
    margin: 0 auto;
  }

  h1 {
    margin-bottom: 0.5rem;
  }

  p {
    margin-top: 0;
  }

  .hero {
    height: calc(100vh - 16px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }

  .full-height {
    height: 100vh;
    display: flex;
    justify-content: center;
  }
</style>

You probably notice that we import the AnimatedElement component - we will create it in the next step.

Add [object Object] component

Create a file called AnimatedElement.svelte and add this code:

<script lang="ts">
  import { fade } from 'svelte/transition';
  import { inview } from 'svelte-inview';

  let isInView;
</script>

<div
  class="wrapper"
  use:inview={{ unobserveOnEnter: true, rootMargin: '-20%' }}
  on:change={({ detail }) => {
    isInView = detail.inView;
  }}
>
  {#if isInView}
    <div in:fade class="box">
      <h3>Appears from nowhere</h3>
      <p>
        Lorem ipsum, dolor sit amet consectetur adipisicing elit. Error,
        adipisci nihil iste.
      </p>
    </div>
  {/if}
</div>

<style>
  .wrapper {
    margin-top: 30px;
  }

  .box {
    width: 300px;
    border: 1px solid rgb(221, 221, 221);
    padding: 25px;
    border-radius: 10px;
  }
</style>

What is going on here? First we import the inview action and animation from svelte.

Then we assign the action to the wrapper with some options:

  • unobserveOnEnter: true - this means that once element is on the screen and animated, it won't be animated again
  • `rootMargin: '-20%' - this means that element will be considered "on screen" once it'll reach 20% of the monitor - better explanation here

Next step is to handle the element entry - we assign the inView flag from the action to our local variable - isInView. This allows us to determine if element should be animated.

Then we check if isInView is true, if it is we animate the element into view by adding the in:fade action.