---
name: logo-reveal-animation
description: Use when you want "Premium, branded, memorable" - Animates a logo into view through drawing, masking, or motion.
---

# Logo Reveal Animation

> **Category:** Loading / Transition  -  **Personality:** Premium, branded, memorable
>
> **Best use:** Homepages, intros, launch screens
>
> **Ratings:** Professional 5/5 - Casual 3/5 - Exotic 3/5 - Visual impact 4/5 - Difficulty 3/5 - Performance cost 2/5

## What it is

A short, choreographed sequence that brings a brand mark to life: an inline‑SVG logo **draws itself on** stroke‑by‑stroke, fills and "blooms", then the wordmark **rises out of a mask** while a loader fills to 100%. It is the kind of thing you put on a homepage hero, a splash/launch screen, or a route preloader — the half‑second that makes a brand feel intentional and expensive. The whole reveal is pure CSS + SVG; JavaScript only restarts it (for a **Replay** button) and ticks the loading counter.

## Dependencies / CDN

**None — pure CSS + inline SVG, with ~25 lines of vanilla JS for Replay + counter.** No animation library.

The demo only loads two display fonts (optional — swap for your brand's type):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
```

## HTML

The mark is **inline SVG** so CSS can animate its strokes. Every stroked shape gets `pathLength="1"` — that normalises any circle / ellipse / path to a length of `1`, so one dash rule draws them all. The wordmark wraps each letter in an `overflow:hidden` clip for the mask‑rise.

```html
<div class="lr-stage">                          <!-- add .lr-go to play -->
  <span class="lr-bloom" aria-hidden="true"></span>

  <svg class="lr-mark" viewBox="0 0 220 220" role="img" aria-label="Meridian emblem">
    <defs>
      <linearGradient id="lrGold" x1="0" y1="0" x2="1" y2="1">
        <stop offset="0" stop-color="#f7e6bd"/><stop offset=".5" stop-color="#e4c684"/><stop offset="1" stop-color="#c39a45"/>
      </linearGradient>
    </defs>
    <!-- fill sits behind the stroke; fades in after the outline is drawn -->
    <path class="lr-starfill" d="M110 64 124 96 150 110 124 124 110 156 96 124 70 110 96 96 Z" fill="url(#lrGold)"/>
    <g class="lr-mg" fill="none" stroke="url(#lrGold)" stroke-linecap="round" stroke-linejoin="round">
      <circle  class="lr-draw" pathLength="1" cx="110" cy="110" r="82"         stroke-width="2"   style="--dl:.25s;--dur:1.15s"/>
      <ellipse class="lr-draw" pathLength="1" cx="110" cy="110" rx="82" ry="28" stroke-width="1.4" style="--dl:.55s;--dur:1s"/>
      <ellipse class="lr-draw" pathLength="1" cx="110" cy="110" rx="28" ry="82" stroke-width="1.4" style="--dl:.75s;--dur:1s"/>
      <path    class="lr-draw" pathLength="1" d="M110 64 124 96 150 110 124 124 110 156 96 124 70 110 96 96 Z" stroke-width="2.4" style="--dl:1.15s;--dur:.85s"/>
    </g>
  </svg>

  <!-- wordmark: one overflow:hidden clip per letter, --i drives the stagger -->
  <h2 class="lr-word" aria-label="Meridian">
    <span class="lr-clip"><span class="lr-ch" style="--i:0">M</span></span>
    <span class="lr-clip"><span class="lr-ch" style="--i:1">E</span></span>
    <!-- …one .lr-clip > .lr-ch per remaining letter… -->
  </h2>

  <button class="lr-replay" type="button">Replay</button>
</div>
```

## CSS

```css
/* 1) THE DRAW-ON — the core of the effect.
   pathLength="1" means every shape's length is 1, so dasharray:1 / dashoffset:1
   hides it and animating the offset to 0 "draws" it. */
.lr-draw{ stroke-dasharray:1; stroke-dashoffset:1; }
.lr-go .lr-draw{ animation:lr-draw var(--dur,1.1s) cubic-bezier(.65,0,.22,1) var(--dl,0s) forwards; }
@keyframes lr-draw{ to{ stroke-dashoffset:0; } }

/* fill + dots wait, then fade in once the outline exists */
.lr-starfill{ opacity:0; }
.lr-go .lr-starfill{ animation:lr-fade .7s ease 1.85s forwards; }
@keyframes lr-fade{ to{ opacity:1; } }

/* 2) THE WORDMARK MASK-RISE — letters start below a clipping box and rise in,
   staggered by their index --i. */
.lr-word{ font-family:'Cormorant Garamond',serif; letter-spacing:.22em; text-transform:uppercase; }
.lr-clip{ display:inline-block; overflow:hidden; vertical-align:bottom; line-height:1; }
.lr-ch{ display:inline-block; opacity:0; transform:translateY(116%) rotate(7deg); }
.lr-go .lr-ch{ animation:lr-rise .85s cubic-bezier(.2,.85,.25,1) forwards;
               animation-delay:calc(1.72s + var(--i)*.06s); }
@keyframes lr-rise{ to{ opacity:1; transform:translateY(0) rotate(0); } }

/* 3) DRESSING — glow bloom behind the mark + a one-shot sheen sweep */
.lr-bloom{ opacity:0; transform:translate(-50%,-50%) scale(.6); }
.lr-go .lr-bloom{ animation:lr-bloom 1.3s ease 1.15s forwards; }
@keyframes lr-bloom{ 0%{opacity:0;transform:translate(-50%,-50%) scale(.6)} 100%{opacity:.9;transform:translate(-50%,-50%) scale(1)} }
.lr-go .lr-mark{ animation:lr-glow 1.4s ease 1.5s both; }
@keyframes lr-glow{ from{filter:drop-shadow(0 0 0 rgba(230,200,127,0))} to{filter:drop-shadow(0 6px 26px rgba(230,200,127,.45))} }

/* 4) RESPECT REDUCED MOTION — show the finished logo, skip the animation */
@media (prefers-reduced-motion: reduce){
  .lr-draw{ stroke-dashoffset:0; }
  .lr-ch,.lr-starfill,.lr-bloom{ opacity:1; }
  .lr-ch{ transform:none; }
  .lr-bloom{ transform:translate(-50%,-50%) scale(1); }
}
```

The base state (no `.lr-go`) is the *hidden* state; the resting state is "drawn". A `<noscript>` style block forces the finished state so the logo is still visible if JS never runs.

## JavaScript

Not required for the reveal itself. It is used only to **restart** the sequence (Replay) and to drive the loading counter in lock‑step with the CSS timeline.

```js
(function(){
  var stage  = document.querySelector('.lr-stage'),
      num    = stage.querySelector('.lr-num'),
      btn    = stage.querySelector('.lr-replay'),
      reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
  var raf, timer, DURATION = 2100, DELAY = 200;          // mirror the CSS timing

  function count(){                                       // 0 → 100, easeOutCubic
    cancelAnimationFrame(raf); clearTimeout(timer);
    if(reduce){ num.textContent = '100'; return; }
    num.textContent = '0';
    var t0 = null;
    function step(ts){
      if(t0 === null) t0 = ts;
      var p = Math.min(1, (ts - t0) / DURATION);
      num.textContent = Math.round((1 - Math.pow(1 - p, 3)) * 100);
      if(p < 1) raf = requestAnimationFrame(step); else num.textContent = '100';
    }
    timer = setTimeout(function(){ raf = requestAnimationFrame(step); }, DELAY);
  }

  function play(){                                        // the canonical restart trick
    stage.classList.remove('lr-go');
    void stage.offsetWidth;                               // force reflow → animations re-run
    stage.classList.add('lr-go');
    count();
  }

  btn.addEventListener('click', play);
  play();                                                 // auto-play on load
})();
```

## How it works

1. **`pathLength="1"` + `stroke-dashoffset`.** Setting `pathLength="1"` normalises every shape's geometric length to `1`, regardless of its real size. With `stroke-dasharray:1` the dash is exactly as long as the path and `stroke-dashoffset:1` pushes it entirely out of view; animating the offset down to `0` slides the dash back in, so the line appears to *draw itself*. One CSS rule covers a circle, two ellipses and a star.
2. **A staggered timeline.** Each shape carries its own `--dl` (delay) and `--dur` (duration), so the ring leads, the meridians follow, and the compass star lands last — read as a single choreographed gesture rather than four things animating at once.
3. **Outline → fill.** The strokes draw the outline; a sibling `<path>` (and the cardinal dots) fade in afterwards to "ink" the mark solid, with a radial **bloom** and a `drop-shadow` glow timed to peak as the mark completes.
4. **Mask‑rise wordmark.** Each letter sits in an `overflow:hidden` clip and starts translated 116% *below* it; raising it to `translateY(0)` makes the letter appear to rise out of an invisible mask. `--i` offsets each letter's `animation-delay` for the wave.
5. **Replay = restart.** Removing `.lr-go`, forcing a reflow (`void stage.offsetWidth`), then re‑adding it makes the browser run every keyframe from the top again.

## Customization

- **Your logo:** drop in your own SVG. Add `class="lr-draw" pathLength="1"` to every shape you want drawn (it works on `path`, `circle`, `ellipse`, `line`, `polyline`, `polygon`, `rect`) and give each a `--dl` / `--dur`. Stroked, mono‑line marks draw best.
- **Pace & feel:** scale all `--dl`/`--dur` values together to speed up or slow down; swap the `cubic-bezier` for snappier or more languid motion.
- **Palette:** the whole demo is gold‑on‑ink via the `lrGold` gradient + the `--gold*` custom properties — retint both to rebrand. The mark works equally well as a flat single colour.
- **Reveal style:** keep the draw‑on, or replace it with a **mask wipe** (`clip-path:inset()` on a solid logo) for a different flavour — the surrounding choreography is identical.
- **No loader?** Delete the `.lr-load` block and the `count()` function; the reveal is independent of it.

## Accessibility & performance

- **Cheap:** animates only `stroke-dashoffset`, `transform`, `opacity` and `filter` — all GPU‑friendly, no layout thrash. The mark is vector, so it stays crisp at any size and adds no image weight.
- **Reduced motion:** a `@media (prefers-reduced-motion: reduce)` block snaps every layer to its finished state and the JS sets the counter straight to `100` — users who opt out see the logo, never the motion.
- **Semantics:** give the `<svg>` `role="img"` + `aria-label`, and put an `aria-label` on the wordmark so the split letters are announced as one word, not "M‑E‑R‑I‑D‑I‑A‑N". Decorative layers are `aria-hidden`.
- **Graceful no‑JS:** a `<noscript>` style block forces the completed state, so the brand is visible even if scripts fail; the Replay button is a pure enhancement.

## Gotchas

- **Forgetting `pathLength`** means you must measure each path with `getTotalLength()` in JS and set the dash values per element — `pathLength="1"` lets you skip all of that and use one rule.
- **`stroke-dasharray` needs a stroke.** Shapes that are `fill`‑only won't draw; give them a stroke (you can fade the fill in afterwards, as the star does).
- **Restart needs the reflow.** Re‑adding the class without the `void stage.offsetWidth` reflow won't re‑trigger CSS animations — the browser sees no change.
- **Overflow clips the mask‑rise**, which is the point — but it also means descenders/round overshoots can be cut. Keep `line-height` tight and the clip box sized to the glyphs.
- **Animating `filter`/`box-shadow` blur is expensive.** Animate the glow's *opacity* or `drop-shadow` spread, not a large changing blur radius, on lower‑end devices.
- **Closed shapes hide the cap.** Round `stroke-linecap` looks best while a path draws; on a closed circle/ellipse the start and end meet so you won't see a cap gap anyway.
