# How to animate a website: six building blocks with free GSAP code

> Six building blocks of an animated website: load-in, scroll reveals, scrubbed scenes, text, hover and page transitions, with free GSAP code and a landing page.

Published 2026-11-09 by Jake. Canonical: https://gsapvault.com/blog/how-to-animate-a-website

---
To animate a website, pick the moments where motion does a job: the first load, content arriving as you scroll, one scroll-driven scene, headlines, pointer feedback and the move between pages. Animate only transform and opacity, and put every animation behind a reduced-motion check.

All of it can be done for free. CSS covers the simple cases, and GSAP, which is free including every plugin, covers the rest. Below is each building block with code you can paste, then all of them assembled into one animated landing page.

## What a fully animated website is

"Fully animated" does not mean everything moves. Sites people describe that way share a pattern: a choreographed first load, content that arrives as you scroll, one or two scenes tied directly to the scrollbar, type that animates in, interface elements that answer the pointer, and transitions that make separate pages feel like one place.

What makes them feel finished is consistency. The same easing, similar durations and one direction of travel run through every section, so the motion reads as a system rather than a collection of effects. Treat motion as a design layer with rules, the same as type and colour.

## Where animation helps a website, and where it hurts

Animation earns its place when it does one of these jobs:

- **Orientation**: showing where something came from or went, such as a menu opening from its button or a card expanding into a page.
- **Attention**: sequencing a hero so the headline lands before the call to action.
- **Feedback**: confirming a hover, a press or a form submission.
- **Explanation**: a product story where scroll position drives a diagram or a series of steps.
- **Perceived speed**: a skeleton or progress indicator that shows something is happening.

It hurts when it gets in the way:

- Content stays hidden until an animation plays, so a slow script or a fast scroller sees blank sections.
- A preloader holds a page that was already ready.
- Scrolling stops responding to the wheel, trackpad or keyboard the way the visitor expects.
- Every element moves, which reads as noise and costs frames.
- Large, fast movement plays for visitors who have asked their system for less motion.

A workable test: if removing an animation loses nothing a visitor would miss, remove it.

## How to animate a website for free

There are two free routes, and most animated sites use both.

**CSS** handles hover states, simple transitions and, in current browsers, scroll-driven entrances. If your site needs nothing more, you may not need a library: the free [CSS Scroll Reveal](/effects/css-scroll-reveal) effect does fade, slide and scale entrances with no animation JavaScript. [GSAP vs CSS animations](/blog/gsap-vs-css-animations) covers where the line falls.

**GSAP** is for sequences, scroll-scrubbed scenes, text splitting and anything you need to pause, reverse or interrupt. It is free, including every plugin, and has been since version 3.13; GreenSock puts its use at over 11 million websites. The [GSAP licence summary](/gsap-license) explains what GreenSock's terms allow and links the official text.

Load it from the CDN before your own script. The [GSAP CDN page](/gsap-cdn) lists the URL for every plugin at the current version:

```html
```

Then give every animation on the page one home. `gsap.matchMedia()` runs its callback only while the media query matches and reverts everything created inside it when the query stops matching, so visitors who prefer reduced motion get the finished page with nothing hidden:

```javascript
gsap.registerPlugin(ScrollTrigger, SplitText);

const mm = gsap.matchMedia();

mm.add('(prefers-reduced-motion: no-preference)', () => {
  // The load-in, reveal, scrub and text snippets below go here
});
```

The snippets in the next section assume they sit inside that callback unless they open their own.

## The six building blocks of website animations

### 1. The load-in

The first moments set the tone. A short timeline that brings in the header, the intro line and the call to action in order gives the page a deliberate start without delaying it:

```javascript
gsap.timeline({ defaults: { duration: 0.8, ease: 'power3.out' } })
  .from('.site-header', { y: -24, autoAlpha: 0 })
  .from('.hero-intro', { y: 30, autoAlpha: 0 }, 0.4)
  .from('.hero-cta', { y: 20, autoAlpha: 0 }, 0.55);
```

`autoAlpha` animates opacity and toggles `visibility`, so an element cannot be clicked or focused while it is invisible. Overlap the steps with position values, as above, rather than chaining them end to end; a load-in the visitor has to wait through is a delay, not an entrance.

If the page has heavy media to load first, a preloader that follows real loading progress is the honest version. [GSAP preloader](/blog/gsap-preloader) builds one with a counter and a curtain exit, and the [Page Preloader](/effects/page-preloader) effect is the finished version with four exit styles.

### 2. Scroll reveals

The most common website animation: sections and cards fade and rise as they enter the viewport. `ScrollTrigger.batch()` groups the elements that enter together, so a row of cards staggers in as one gesture instead of firing separately:

```javascript
gsap.set('.reveal', { autoAlpha: 0, y: 40 });

ScrollTrigger.batch('.reveal', {
  start: 'top 85%',
  once: true,
  onEnter: (batch) => gsap.to(batch, {
    autoAlpha: 1, y: 0, stagger: 0.1, duration: 0.8, ease: 'power3.out'
  })
});
```

The hidden state is set in JavaScript, not CSS, on purpose. If the script fails, or reduced motion skips the callback, the content is simply there. [Animate on scroll with GSAP ScrollTrigger](/blog/how-to-animate-on-scroll) goes further into toggle actions, scrubbing and pinning.

[Live interactive demo](https://gsapvault.com/demos/image-clip-reveal/index.html)

### 3. Scroll-scrubbed storytelling

A scrubbed animation is tied to the scrollbar: scroll down and it plays, scroll up and it rewinds. Pin the section while that happens and you get the scenes animated sites are known for. Use it for one or two moments that explain something, not for every section:

```javascript
gsap.timeline({
  scrollTrigger: {
    trigger: '.story',
    start: 'top top',
    end: '+=150%',
    scrub: true,
    pin: true
  }
})
  .from('.story-image', { scale: 0.7, borderRadius: 48 })
  .from('.story-caption', { autoAlpha: 0, y: 30 }, '<0.3');
```

The `end` value sets how much scrolling the scene takes: a larger value plays it more slowly. `scrub: true` follows the scrollbar exactly; a number such as `scrub: 1` adds a short catch-up, which smooths out a jumpy mouse wheel.

[Live interactive demo](https://gsapvault.com/demos/parallax-hero/index.html)

### 4. Text animation

Headlines that arrive word by word are a large part of why animated sites feel designed. SplitText wraps text in elements you can animate, and `mask: 'lines'` clips each line so the words rise from behind an edge:

```javascript
SplitText.create('.hero-title', {
  type: 'words, lines',
  mask: 'lines',
  autoSplit: true,
  onSplit: (self) => gsap.from(self.words, {
    yPercent: 100, stagger: 0.05, duration: 0.7, ease: 'power3.out'
  })
});
```

`autoSplit` splits again when fonts load or the container resizes, so the line breaks stay right, and returning the tween from `onSplit` lets SplitText swap it cleanly when that happens. SplitText also labels the original element with its full text by default, so a screen reader reads the sentence, not the fragments.

[GSAP text animation: 6 approaches](/blog/text-animations-gsap) compares split reveals, typewriters, scrambles and scroll highlights. The [Split Text Stagger Reveal](/effects/split-text-reveal) effect is the finished version of this block.

### 5. Hover and cursor micro-interactions

Small responses to the pointer make an interface feel alive more than any large scene does: a button that nudges, an underline that draws, a card that lifts. Gate them on devices that really hover, so touch screens do not get stuck states, and remove the listeners when the query stops matching:

```javascript
mm.add('(prefers-reduced-motion: no-preference) and (hover: hover)', () => {
  const cleanups = gsap.utils.toArray('.button').map((button) => {
    const hover = gsap.timeline({ paused: true })
      .to(button, { scale: 1.04, duration: 0.25, ease: 'power2.out' })
      .to(button.querySelector('.arrow'), { x: 6, duration: 0.25, ease: 'power2.out' }, 0);

    const enter = () => hover.play();
    const leave = () => hover.reverse();
    button.addEventListener('pointerenter', enter);
    button.addEventListener('pointerleave', leave);

    return () => {
      button.removeEventListener('pointerenter', enter);
      button.removeEventListener('pointerleave', leave);
    };
  });

  return () => cleanups.forEach((cleanup) => cleanup());
});
```

A timeline you play and reverse handles a fast in-and-out without jumping, which separate enter and leave tweens often do. Give keyboard users the same feedback with `:focus-visible` styles. For link underlines, the free [Hover Underline](/effects/hover-underline) effect has four styles; for cursor followers and trails, [Custom cursor with GSAP](/blog/custom-cursor-effects-wordpress) has the code, which works on any site.

### 6. Page transitions

On a multi-page site, the move between pages is the moment most sites leave unanimated. The lightest option is the browser's own cross-document View Transitions, which crossfade between real page loads with one CSS rule on both pages:

```css
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}
```

Browsers without support load the next page as normal. For an exit that starts on the click and plays while the next page loads, or a signature transition with a title card, use GSAP. [GSAP page transitions: seven patterns with code](/blog/gsap-page-transitions) covers both routes, including focus handling for screen readers.

## How to animate a landing page

A landing page is the easiest place to start: one page with one goal. Use the blocks in the order a visitor meets them:

1. **Load-in** on the hero: the headline word by word, then the intro and the call to action.
2. **Scroll reveals** for the feature cards and the closing call to action.
3. **One scrubbed scene** for the product: the moment that shows what it does.
4. **Hover feedback** on every button.

Leave pricing tables and forms still. That is where visitors read carefully and decide, and motion there competes with the decision.

Here is the whole page as one file. Save it as `index.html` and open it:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Animated landing page</title>
  <style>
    * { box-sizing: border-box; margin: 0; }
    body { font-family: system-ui, sans-serif; background: #101217; color: #f1f1f1; line-height: 1.5; }
    .site-header { display: flex; justify-content: space-between; align-items: center; padding: 1.25rem 5vw; }
    .hero { min-height: 90vh; display: grid; place-content: center; gap: 1.5rem; padding: 0 5vw; text-align: center; }
    .hero-title { font-size: clamp(2.5rem, 8vw, 6rem); line-height: 1.05; }
    .button { display: inline-flex; gap: 0.5rem; justify-self: center; padding: 0.9rem 1.5rem; border-radius: 999px; background: #c8f560; color: #101217; font-weight: 600; text-decoration: none; }
    .button:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
    .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.5rem; padding: 6rem 5vw; }
    .card { padding: 2rem; border-radius: 1rem; background: #1c1f27; }
    .story { position: relative; height: 100vh; display: grid; place-items: center; overflow: hidden; }
    .story-image { width: min(80vw, 900px); aspect-ratio: 16 / 9; border-radius: 1rem; background: linear-gradient(135deg, #c8f560, #3a7bd5); }
    .story-caption { position: absolute; bottom: 10vh; font-size: 1.25rem; }
    .closing { display: grid; gap: 1.5rem; padding: 8rem 5vw; text-align: center; }
  </style>
</head>
<body>
  <header class="site-header">
    <strong>Northwind</strong>
    <a class="button" href="#start">Start free <span class="arrow" aria-hidden="true">→</span></a>
  </header>

  <main>
    <section class="hero">
      <h1 class="hero-title">Plan the week in one view</h1>
      <p class="hero-intro">Shifts, bookings and staff in a single calendar your whole team can read.</p>
      <a class="button hero-cta" href="#start">Try it free <span class="arrow" aria-hidden="true">→</span></a>
    </section>

    <section class="features">
      <article class="card reveal"><h2>Drag to plan</h2><p>Move a shift and the totals update.</p></article>
      <article class="card reveal"><h2>Share a link</h2><p>Staff see their week without logging in.</p></article>
      <article class="card reveal"><h2>Spot the gaps</h2><p>Uncovered hours are marked before they happen.</p></article>
    </section>

    <section class="story">
      <div class="story-image" role="img" aria-label="Product screenshot"></div>
      <p class="story-caption">Every shift, every site, one screen.</p>
    </section>

    <section class="closing" id="start">
      <h2 class="reveal">Ready when your team is</h2>
      <a class="button reveal" href="#">Create a schedule <span class="arrow" aria-hidden="true">→</span></a>
    </section>
  </main>

  </body>
</html>
```

Switch on reduced motion in your system settings and reload: the same page appears complete and still. That is the version a visitor gets if the scripts fail to load, too.

For landing pages with real content and art direction already worked out, browse the [animated landing page templates](/templates/category/landing-marketing). The free [Waitlist Page template](/templates/waitlist-page-template) is a one-screen pre-launch page you can download and read through first.

## Reduced motion and accessibility

Motion can make some visitors dizzy or nauseous, and every major operating system has a setting to ask for less of it. The `gsap.matchMedia()` wrapper above is the main defence, and a few habits cover the rest:

- **Reduced motion is not no motion.** For visitors who prefer it, a short opacity fade is usually fine. Add a second `mm.add('(prefers-reduced-motion: reduce)', ...)` for gentle versions of anything that carries meaning.
- **Keep content visible by default.** Set hidden starting states in JavaScript, never in CSS, so a failed script does not leave blank sections.
- **Give looping motion a pause.** Anything that moves on its own for more than five seconds alongside other content, such as a marquee or an animated background, needs a way to stop it (WCAG 2.2.2).
- **Match hover with focus.** Keyboard users should get the same feedback as mouse users.
- **Leave scrolling alone.** If you add smooth scrolling, check that the keyboard, in-page links and the browser's back button still behave.

## Performance: keep website animations smooth

Animation that stutters looks worse than no animation. Most jank comes from a handful of causes:

- **Animate transform and opacity.** In GSAP that means `x`, `y`, `scale`, `rotation` and `autoAlpha`. Animating `width`, `height`, `top` or `left` makes the browser recalculate layout on every frame.
- **Batch scroll reveals.** One `ScrollTrigger.batch()` for a grid is lighter than a separate trigger for every card.
- **Give images dimensions.** Set `width` and `height` (or `aspect-ratio`) so the layout does not shift after ScrollTrigger has measured where everything starts, and call `ScrollTrigger.refresh()` after you inject content.
- **Test on a mid-range phone.** A desktop hides problems that a phone shows straight away.

The [GSAP performance guide](/blog/gsap-performance-guide) covers compositing, smooth scroll and pinned sections that jump.

## When to start from a template instead

Build it yourself when you are adding one or two of these blocks to a site that already exists. Start from a template when you need the whole site: the easing, the durations and the handoffs between sections are already decided, and the reduced-motion and mobile versions already exist, so you are editing content instead of tuning timings.

To start without spending anything, the [free GSAP Vault products](/free) collect every free effect, template and UI element in one place, MIT licensed and downloadable without an account. They are also mirrored in the [free GitHub repo](https://github.com/jaydickinson/free-gsap-effects). To see finished animated sites across portfolios, venues, shops and SaaS, the [animated website templates roundup](/blog/interactive-website-templates) walks through them one by one, and the [templates page](/templates) filters them by category.

---

*Want the finished versions of these blocks? Browse [GSAP scroll animations](/categories/gsap-scroll-animations), [text effects](/categories/gsap-text-effects), [hover effects](/categories/gsap-hover-effects) and [loading animations](/categories/gsap-loading-animations), or start with [everything that is free](/free).*