25 CSS Text Animations 24 / 25

CSS Mask Wipe Text Reveal Animation

A sharp-edged mask wipes across text to reveal it — using CSS mask-image with a gradient that hard-transitions at a moving boundary.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ta-24">
  <div class="ta-24__stage">
    <p class="ta-24__eyebrow">Revealing</p>
    <h2 class="ta-24__title" id="ta-24-title">The Future</h2>
    <h2 class="ta-24__title ta-24__title--2" id="ta-24-title2">Is Now</h2>
    <button class="ta-24__btn" id="ta-24-replay">↺ Replay wipe</button>
  </div>
</div>
.ta-24, .ta-24 *, .ta-24 *::before, .ta-24 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.ta-24 ::selection { background: #0f766e; color: #fff; }

.ta-24 {
  --bg: #030d0b;
  --teal: #2dd4bf;
  min-height: 100vh;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  font-family: 'Syne', 'Helvetica Neue', sans-serif;
}

.ta-24__stage { text-align: left; }

.ta-24__eyebrow {
  font-size: 0.7rem;
  color: #134e4a;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  margin-bottom: 0.4rem;
}

.ta-24__title {
  font-size: clamp(2.2rem, 7vw, 4.5rem);
  font-weight: 900;
  letter-spacing: -0.02em;
  color: var(--teal);
  line-height: 1.05;
  -webkit-mask-image: linear-gradient(to right, #000 50%, transparent 50%);
  mask-image: linear-gradient(to right, #000 50%, transparent 50%);
  -webkit-mask-size: 200% 100%;
  mask-size: 200% 100%;
  -webkit-mask-position: -100% 0;
  mask-position: -100% 0;
}

.ta-24__title--2 {
  color: #e2e8f0;
  -webkit-mask-position: -100% 0;
  mask-position: -100% 0;
}

.ta-24__title.wipe-in {
  animation: ta-24-wipe 0.9s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

.ta-24__title--2.wipe-in {
  animation: ta-24-wipe 0.9s cubic-bezier(0.4, 0, 0.2, 1) 0.25s forwards;
}

@keyframes ta-24-wipe {
  from { -webkit-mask-position: -100% 0; mask-position: -100% 0; }
  to   { -webkit-mask-position:    0% 0; mask-position:    0% 0; }
}

.ta-24__btn {
  margin-top: 1.2rem;
  display: block;
  font-family: inherit;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: none;
  border: 1px solid #134e4a;
  color: #0f766e;
  padding: 0.4rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}
.ta-24__btn:hover { border-color: #2dd4bf; color: #2dd4bf; }

@media (prefers-reduced-motion: reduce) {
  .ta-24__title { -webkit-mask-image: none; mask-image: none; animation: none !important; }
}
(function() {
  const t1  = document.getElementById('ta-24-title');
  const t2  = document.getElementById('ta-24-title2');
  const btn = document.getElementById('ta-24-replay');
  if (!t1 || !t2) return;

  function play() {
    [t1, t2].forEach(el => {
      el.classList.remove('wipe-in');
      void el.offsetWidth;
      el.classList.add('wipe-in');
    });
  }

  play();
  if (btn) btn.addEventListener('click', play);
})();

How this works

A mask-image: linear-gradient() is applied to the text element with a hard-stop from fully opaque to fully transparent at a single pixel boundary. Unlike a soft gradient, the stop is placed at the same position twice — e.g., rgba(0,0,0,1) 50%, rgba(0,0,0,0) 50% — creating a razor-sharp wipe edge. The mask-position is then animated from -100% 0 to 100% 0, sweeping this hard boundary across the text from left to right.

JavaScript triggers the reveal by adding an animation class after a configurable delay, enabling scroll-triggered or interaction-triggered reveals. The mask-size is set to 200% 100% so the gradient pattern is double the element width, giving room to pan the hard edge fully across without the opaque region disappearing before the wipe completes.

Customize

  • Change wipe direction by using a mask-image: linear-gradient(to bottom, ...) and animating mask-position-y from -100% to 100% for a vertical curtain reveal.
  • Soften the wipe edge by spreading the gradient stop over 10% — e.g., 100% 45%, 0% 55% — for a semi-transparent leading edge like a cloth wipe.
  • Trigger the wipe on button click by attaching the animation class to a click handler on a sibling button element.
  • Chain multiple wipes on multiple lines with staggered delays to reveal a full paragraph progressively from top to bottom.
  • Add a coloured wipe-bar element — a thin div that tracks the mask boundary — by syncing a translateX animation of the same duration to visually show the wipe front.

Watch out for

  • mask-image requires the -webkit-mask-image prefix in Safari and older Chrome builds — always declare both properties in the same rule block.
  • The mask applies to the element and all its children — if the element has a background, that too will be masked. Apply only to text-only elements or use a wrapper to isolate the mask.
  • Animating mask-position directly is not universally composited — in Firefox it may trigger layout-dependent repaints. Prefer mask-position over mask-size changes for smoother frame rates.

Browser support

ChromeSafariFirefoxEdge
53+ 15.4+ 53+ 53+

mask-image is supported in all modern browsers; use -webkit-mask-image prefix for Safari. Firefox added unprefixed support in v53 but prefixed versions work from earlier.

Search CodeFronts

Loading…