Frontend.fyi

Tutorial

Recreating Vercel's Smooth Navigation Animation With CSS Only

In this video we'll be recreating an animation that shines in its simplicity.

Playground settings

Title

Description

Public

Editor settings


Packages

These packages can be imported in your JavaScript files.

Package name

Version

@

@

@

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
import { FefyiLogo } from "./fefyi";
import "./index.css";
import useScrollPosition from "@react-hook/window-scroll";
import { useMemo } from "react";

const useRange = (
  num: number,
  inMin: number,
  inMax: number,
  outMin: number,
  outMax: number,
) => {
  const mappedValue = useMemo(() => {
    const newValue =
      ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
    const largest = Math.max(outMin, outMax);
    const smallest = Math.min(outMin, outMax);
    return Math.min(Math.max(newValue, smallest), largest);
  }, [num]);

  return mappedValue;
};

function App() {
  const y = useScrollPosition(60);
  const navX = useRange(y, 0, 50, 0, 42);
  const logoScale = useRange(y, 0, 50, 1, 0.8);

  return (
    <div className="font-sans">
      <header className="flex gap-4 bg-black px-6 py-4 pl-16 text-sm">
        <svg
          style={{
            transform: `scale(${logoScale})`,
          }}
          className="fixed left-6 top-4 z-10"
          aria-label="Vercel Logo"
          fill="white"
          viewBox="0 0 75 65"
          height="22"
        >
          <path d="M37.59.25l36.95 64H.64l36.95-64z"></path>
        </svg>
        <ol className="flex gap-4">
          <li aria-hidden className="text-zinc-700">
            /
          </li>
          <li className="flex items-center gap-2">
            <span className="inline-flex h-5 w-5 items-center justify-center rounded-full border border-zinc-700 bg-black p-1">
              <FefyiLogo />
            </span>
            frontendfyi
          </li>
          <li aria-hidden className="text-zinc-700">
            /
          </li>
          <li className="flex items-center gap-2">
            <span className="inline-flex h-5 w-5 items-center justify-center rounded-full border border-zinc-700 bg-black p-1">
              <FefyiLogo />
            </span>
            courses
          </li>
        </ol>
      </header>
      <nav className="sticky top-0 flex border-b border-zinc-700 bg-zinc-900 text-sm">
        <ol
          style={{
            transform: `translateX(${navX}px)`,
          }}
          className="relative flex gap-4 px-6 py-4 text-zinc-400"
        >
          <li>Project</li>
          <li className="text-zinc-200">Deployments</li>
          <li>Analytics</li>
          <li>Speed Insights</li>
          <li>Logs</li>
          <li>Storage</li>
          <li>Settings</li>
        </ol>
      </nav>
      <main className="mx-auto my-12 max-w-5xl rounded-md border border-zinc-800">
        <table className="w-full">
          <tbody>
            {[...Array(100)].map((_, i) => (
              <tr key={i} className="h-12 border-b border-zinc-800 bg-zinc-950">
                <td />
              </tr>
            ))}
          </tbody>
        </table>
      </main>
    </div>
  );
}

export default App;
One sec — editor's thinking…

This week I noticed Vercel added a new scroll animation to their deployments page. My mind immediately thought this was such a nice animation that I wanted to recreate with Framer Motion. Then I thought; Why Framer Motion?! This is such a simple animation, we can do this with plain CSS!

That is exactly what we will be exploring in todays video. We are gonna recreate this smooth scroll animation with just a few lines of CSS and a single JavaScript scroll listener. Let’s dive in!

Eager to learn more?

I think this video might be a good fit for you too!