Skip to content

A reusable animate-on-scroll component for SvelteKit

Animate on scroll cover image
Originally written in June 2022. The code below is Svelte 4 (export let, <slot>) and predates runes.

Ever needed to animate in an element once it's visible on the screen? There are multiple ways to achieve this, with even more ways to handle the animation. I'm using svelte-intersection-observer to track when the element is in view, and TailwindCSS classes for the animations.

Check out the code: <AnimatedElement>

AnimatedElement.svelte
<script>
  import IntersectionObserver from "svelte-intersection-observer";
  let element;
  let intersecting;
  export let duration = "700ms";
  export let delay = "0";
  export let fade = false;
  export let fly = false;
  export let offset = "0";
  export let once = true;
  // Set the default to fly if 'fly' or 'fade' are not specified
  fly = !fade && !fly ? true : false;
</script>
<IntersectionObserver
  rootMargin={`${offset}px`}
  {once}
  {element}
  bind:intersecting
>
  {#if fade}
    <div
      bind:this={element}
      class={`${intersecting ? "opacity-100" : "opacity-0"} transition`}
      style="transition-delay: {delay}; transition-duration: {duration}"
    >
      <slot {intersecting} />
    </div>
  {/if}
  {#if fly}
    <div
      bind:this={element}
      class={`${
        intersecting ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-6"
      } transform transition`}
      style="transition-delay: {delay}; transition-duration: {duration}"
    >
      <slot {intersecting} />
    </div>
  {/if}
</IntersectionObserver>

I know this code could be shortened, but I'm not that concerned with that. I'd rather have a component I can easily read. Also note, I'm passing intersecting back into the slot, in case you need the trigger wherever you use the component

How to use:

<AnimatedElement>
   <a href="/">
      <img src="/img/logo.svg" class="h-16 md:h-20 lg:h-auto" alt="logo" />
    </a>
</AnimatedElement>

Potential improvements

I didn't have time to lollygag when coding this, but I would like to make this without needing the extra wrapper div inside the <AnimatedElement> component. I don't see why this wouldn't be possible by using the <svelte:element this={tag}> tag and passing the element tag along with the class into the component. Like this <AnimatedElement tag="div" class="flex">. Let me know you'd like for me to code that out.

Thanks for reading! Now go grab some pizza!