Skip to content
animorio
IT
All components

The viewfinder nav

Navigation, Hover, TransitionsGSAPMediumCompressed weight: Demo 4.8 KBGSAP 28 KBFreeDownload the zip

A camera's focus frame sits on the current link. Pointing at another link the frame slides over and hunts for focus, opening and closing while the word goes from blurred to sharp, then its brackets blink green; a click takes the picture, with the viewfinder going black for an instant and a flash of light in the frame.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The Vue tab holds the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The viewfinder nav</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/spline-sans-mono@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the nav: the focus frame of a camera sits on the current link; pointed at, another link is brought into focus,
         and a press takes the picture, with the viewfinder going black for an instant -->
    <nav class="vf" aria-label="Main" data-nav>
      <ul class="vf__list" data-list>
        <li><a class="vf__link" href="#" aria-current="page" data-link><span data-word>Portraits</span></a></li>
        <li><a class="vf__link" href="#" data-link data-autoclic><span data-word>Weddings</span></a></li>
        <li><a class="vf__link" href="#" data-link data-autoclic><span data-word>Travel</span></a></li>
        <li><a class="vf__link" href="#" data-link data-autoclic><span data-word>About</span></a></li>
      </ul>
      <!-- the faint frame that stays on the current link, and the focus frame that moves -->
      <span class="vf__frame vf__frame--ghost" data-ghost aria-hidden="true"><i></i><i></i><i></i><i></i></span>
      <span class="vf__frame vf__frame--af" data-af aria-hidden="true"><b class="vf__flash" data-flash></b><i></i><i></i><i></i><i></i></span>
      <span class="vf__shutter" aria-hidden="true"><i data-blade></i><i data-blade></i></span>
    </nav>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

14 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The viewfinder nav” from Animorio and fit it into my site

What it does

On a black background four uppercase links in a monospaced typeface, Portraits, Weddings, Travel and About, grey except the current one, light. On arrival a frame of four corner brackets comes in wide and see-through and closes in on the current link hunting for focus, while the word goes from blurred to sharp; in focus, the brackets turn green and blink. With the mouse on another link the frame slides over, white and with longer brackets, opens up, closes in past the word, opens a little and stops, while the word, which lights up, blurs and turns sharp again; in focus the brackets blink green and shorten. A thin grey frame stays on the current link. Leaving the row the frame goes back to the current link. The click takes the picture: two black blades close over the row from above and below and open again, the brackets squeeze and spring back, a white light comes on in the frame and fades, and the link becomes the current one. Tapping a link not yet in focus, the picture is taken as soon as the frame brings it into focus.

How it works

A frame of four brackets

The frame is a span with four i at its corners, each a square of --arm pixels with only two borders, --line thick. The script puts it on a link's box (measured with offsetLeft, offsetTop, offsetWidth and offsetHeight) opened or closed by pad pixels: translate for the place and width and height for the size, so the brackets keep their thickness.

Hunting for focus

A timeline moves the box onto the link with expo.out in SLIDE seconds, while pad goes through the keyframes PAD + 11, PAD − 5, PAD + 3 and PAD in SLIDE × 0.7 plus HUNT seconds. The word pointed at is blurred by |pad − PAD| × 0.22 pixels, 3 at most: it is sharp exactly when the frame stops.

In focus

At the end the frame takes the is-locked class (green, with a 0.12 second colour transition), the line goes from 3 to 2 pixels, the arms go back from 12 to 9 pixels with back.out(3) and the corners blink (opacity from 0.25 to 1, three passes of 0.07 seconds).

The shot

Two black blades, each half the row plus a margin and with its origin on the outer edge, go from scaleY 0 to 1 in BLINK × 0.35 seconds, wait BLINK × 0.15 and open again in BLINK × 0.65; meanwhile a white radial gradient in the frame lights up at 0.7 and fades, and pad drops to PAD − 3 and comes back with elastic.out. A press on a link not yet in focus waits for the focus: the shot starts from there.

Keys, going back and the preview

Tab onto a link (focus-visible) brings the frame there and Enter takes the shot; leaving the row with the pointer (after 0.25 seconds) or the focus brings the frame back to the current link, and the thin frame shows only while the other one is elsewhere. A ResizeObserver measures again when the words change width. The library's preview clicks the other links in turn.

Values to change

NameFileDefaultWhat it changes
SLIDEscript.js0.32The seconds the frame takes to move to another link.
HUNTscript.js0.5The seconds it takes to find focus once there.
BLINKscript.js0.2The seconds of the shutter's blackout.
PADscript.js5How far the frame stands off the word in focus, in pixels.

Try them in the Playground

When to use it

For the nav of people who work with images: a photographer, a video studio, an equipment rental, a film festival, a photography school, a photo app.

On phones

The text goes from 12 to 16 pixels with the width and the gaps shrink, so the four links fit one row even on 390 pixels; each link is 45 pixels tall and a tap focuses and shoots, without waiting for a double tap.

With reduced motion

The frame jumps onto the link without sliding or hunting for focus, the word does not blur, the brackets turn green without blinking, the viewfinder does not go black and the shot is just a short light in the frame; the thin frame on the current link appears and disappears without fading.

Pitfalls

  • The frame changes size with width and height, not with a scale: scaled, it would thicken the lines of the brackets too.
  • The blur filter must be removed altogether when the word is in focus (an empty filter, not blur(0px)): a filter left on, even at zero, has the word drawn on a layer of its own.
  • The brackets' blinking and the thickness of the lines must be stopped and reset every time the frame moves on (killTweensOf and set), or an interrupted frame would stay half see-through.
  • The shot waits for the focus: after a tap on a far link the frame is still travelling, and the shot fires when it finds focus, only if the link in focus is the one pressed.
  • The shutter blades must be the colour of the page and cover a little margin too: they are what hides the row for an instant.

Libraries

GSAP 3.15.0, Spline Sans Mono (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 4.8 KB; the libraries: GSAP 28 KB.

Testing

Tested on 4 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components