Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 13.6Intermediate ~24 min · shot 6 of 6

Build: The Animated Logo and Route Line

In one line

Add logo.svg and route.svg to the launch page, inline. The logo draws itself once on load, the Ardenvale test route draws as you scroll to it, and with reduced motion both are simply there, finished.

0/6 · 0%

Before you start

You will learn

  • Add src/assets/svg/logo.svg and src/assets/svg/route.svg, and paste their markup into index.html
  • Add the Phase 13 block to components.css and the route fallback to main.js
  • Check the logo, the route and reduced motion in the editor and on your page

You write code in

In your lumen-motion project (npm run dev) and in the editor on this page: add two files and change three

Files you touch

  • src/assets/svg/logo.svg
  • src/assets/svg/route.svg
  • index.html
  • src/styles/components.css
  • src/js/main.js

Think of it like this

A shop sign that is lit once when the shop opens, and a signpost with the route to the next town that unrolls as you walk up to it. When the shop is closed or you have asked it to stay calm, the sign is simply there, finished.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

inline SVG
SVG markup written inside the HTML page, so that the page's CSS and JavaScript can reach every shape.
resting state
How an element looks when no animation runs. Here it is the finished drawing, so skipping the animation is safe.
route-wait
A class that main.js adds, only in browsers without scroll-driven animation, to hide the route line until it is seen.
view()
A scroll-driven timeline that follows how far an element has travelled across its scroller (Topic 12.2).

Step by step

01Add the two files

Create src/assets/svg/logo.svg and src/assets/svg/route.svg with the contents below. Open one in the browser: you see the finished drawing, in black, with no motion.

02Paste the markup into index.html

Replace the text lumen inside a.logo#logo with the logo's markup (without xmlns), add aria-label to the link, and add a #route section before </main> holding the route's markup.

03Add the CSS and the script

Append the Phase 13 block to components.css. Append the route fallback to main.js: it uses motionAllowed() and onMotionChange() from Phase 7.

04Try it

Press Replay. Scroll to the route. Switch on Reduced motion and reload. Open the page in a browser without scroll-driven animation, or edit the feature test, to see the fallback.

Try it yourself

  1. 1

    Slow the logo down

    Press Replay, pause, and step at 0.1×. The ring draws first (at 250 ms its offset is 0.11), the bolt starts at 350 ms, and the word reaches full opacity at 1100 ms.

  2. 2

    Scroll the route

    Press To route: the page jumps to the map and the line is already drawn (offset 0). Now scroll up over the small page area, a little at a time: the line un-draws, and it is empty again by about 125 px of scroll. Scroll down and it draws again, finished by about 215 px: the scroll position is the clock.

  3. 3

    Run it as a reduced-motion user

    Switch on Reduced motion: the console says logo animations: 0 and route animations: 0, and the finished logo and route are on screen.

Code & diagrams

The logo and the route after Phase 13

The header logo and the route section of the page, with the project's markup, CSS and fallback inlined (local imports do not run in the preview; two lines stand in for prefers-reduced.js). Replay the logo, scroll to the route, and switch on Reduced motion.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

scroll-driven animation supported: true
logo animations: 3
route animations: 1

Your project files

Your lumen-motion project after this topic. Click a file to see it in full.

src/assets/svg/logo.svgsvg

The whole file after this topic. If yours differs, you can copy this one and carry on.

<svg xmlns="http://www.w3.org/2000/svg" class="logo-mark" viewBox="0 0 98 28" height="28" aria-hidden="true" focusable="false"
  fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
  <!-- the headlight ring, then the bolt: each is one dash as long as the whole path (pathLength 1) -->
  <circle class="draw ring" cx="14" cy="14" r="11" pathLength="1" stroke-dasharray="1" />
  <path class="draw bolt" d="M16 6 L10 15 H15 L13 22 L19 12 H14 Z" pathLength="1" stroke-dasharray="1" />
  <!-- the word stays real text, so it can be selected and scaled -->
  <text class="logo-word" x="32" y="21" fill="currentColor" stroke="none" font-family="system-ui, sans-serif" font-size="19" font-weight="800">lumen</text>
</svg>

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget animation-range

In the example's CSS delete animation-range: entry 10% cover 50%;.

terminal
$ Scroll the page step by step
── what you'll see ──
At scroll 200 the line is still 57 percent empty (offset 0.57, against 0.14 with the range), at 280 it is 15 percent empty, and it is complete only at about 310. On a page with little room under the map, it would never finish.

Break #2

Use the file as an image

Replace the inline logo with <img src="/src/assets/svg/logo.svg" alt="Lumen Bikes">.

terminal
$ Reload
── what you'll see ──
The finished logo appears in black, with no drawing and no orange: the page's CSS cannot reach into an image.

Myth vs fact

Myth

A logo that draws itself needs a library or a video.

Fact

Two attributes and a keyframe with only a from: the browser does the rest.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The two paths of the logo are separate elements because the dash pattern restarts on every piece (Topic 13.2): one path would draw both at once.

  • ▸

    With the finished drawing as the resting state, a failed stylesheet, an old browser or animation: none all end in the right picture. The reverse fails to a blank logo.

  • ▸

    The route's view timeline follows the path itself. A map at the very top of a short page is already part-way through its journey at load, and then draws partly or not at all: put the map under enough content, or start the range at 0.

Remember this

  1. 1

    Why the site needs this now (Mei's files): the header shows the word lumen in plain text and the page has no picture of the test route. Mei drew both as vectors. This phase makes them move with no video, no library and under 1 KB of JavaScript.

  2. 2

    What it adds. Two files, logo.svg and route.svg, with the finished drawings written into their attributes (pathLength="1", stroke-dasharray="1", stroke-dashoffset="0"), so each looks right alone. A stand-alone file used as an <img> cannot be reached by the page's CSS, so the page includes the same markup inline: the logo in the header link, the route in a new #route section after the specs. components.css gets a Phase 13 block, and main.js a short fallback. The files below are the page as Phase 12 leaves it (cart drawer, page transitions, scroll story included), so nothing from Phases 0 to 12 is lost. The animations are named draw-in and logo-fade, because earlier examples use a fade-in with other values.

  3. 3

    The logo. The ring draws in 700 ms with the --ease-out curve, the bolt in 500 ms after a 350 ms delay, and the word fades in at 700 ms. The keyframes have only a start (from { stroke-dashoffset: 1 }), so the end is the element's own resting offset of 0. The link keeps aria-label="Lumen Bikes, home" and the SVG is aria-hidden. Measured on the build example: three animations on the logo, and the ring was at offset 0.11 at 250 ms.

  4. 4

    The route. Where the browser supports scroll-driven animation (Topic 12.2), animation-timeline: view() ties the draw to the scroll position. In the example's small scroller (417 px of scrolling), with animation-range: entry 10% cover 50% the line was empty at scroll 120, half drawn at 170 (offset 0.49) and finished by 215. Without the range it was still 57 percent empty at 200 and finished at about 310. Where it is not supported, main.js adds .route-wait (the line is hidden), watches the map with an IntersectionObserver and removes the class when 40 percent is in view, so a CSS transition draws it. With the feature test switched off, the line stayed hidden while the map was out of view, was at offset 0.69 0.7 s after the scroll, and finished about 1.5 s after it.

  5. 5

    Reduced motion. --draw-name, --fade-name and --route-dur follow the same three rules as Phase 10 (device setting, the site toggle of Phase 7, "Full" overriding the device). With motion off the finished drawings show and main.js never adds .route-wait. Measured with the reduced-motion switch on: zero animations on the logo and on the route.

  6. 6

    What the launch site can do now that it could not before: open with a logo that draws itself, show the test route as a line that follows the reader's scroll, and keep both sharp at any size, with no extra download. What is still missing: the launch-day hero timeline and the pinned battery section (Phase 14), the React configurator (Phase 15), the headlight mascot (Phase 16) and the launch confetti (Phase 17).

Explain it without notes

01

Why is the finished logo the resting state, with the keyframes saying only from?

02

How does the route work in a browser without scroll-driven animations?

Practice

01

Add a third stop animation: the circle for Hill Park pops in when the line reaches it. Which timeline and which range would you use?

02

The designers want the route to draw in both directions as you scroll up and down. What does the current CSS do, and why?

Done when you can

  • You can add an inline SVG to a page and keep its accessible name

  • You can draw a logo once and a route with the scroll, with a finished resting state

  • You can reduce the motion by device setting and by the site toggle

  • You can test the fallback branch by switching the feature test off