CSS is awesome, right?

Build the shimmer text effect

The trick is to animate a large gradient background, then clip that moving background into the text shape.

1. Make the highlight background

Start with a gradient. The bright center is the highlight that will later pass through the letters.

<!-- NEW in Step 1: add the text element. -->
<p class="shimmer">CSS is awesome, right?</p>

<style>
  /* NEW in Step 1: create a static highlight background. */
  .shimmer {
    background: linear-gradient(
      90deg,
      #8a8a8a,
      #ffffff,
      #8a8a8a
    );
  }
</style>

2. Clip the background into text

Use transparent text plus background-clip:text. The text becomes a window that reveals the gradient behind it.

.shimmer {
  /* NEW in Step 2: hide the original text fill. */
  color: transparent;
  /* NEW in Step 2: use a colorful gradient behind the transparent text. */
  background: linear-gradient(
    90deg,
    #ff4d6d,
    #ffd166,
    #06d6a0,
    #4cc9f0,
    #8a5cf6
  );
  /* NEW in Step 2: reveal the background through the glyphs. */
  background-clip: text;
  -webkit-background-clip: text;
}

3. Give the gradient room to move

The background is wider than the text. The yellow area represents extra travel distance so the highlight can enter and leave cleanly.

<style>
  /* NEW in Step 3: make the background wider than the text. */
  .shimmer {
    background-size: 400% 100%;
  }
</style>

<script>
  const root = document.documentElement;
  const text = document.querySelector(".shimmer");
  const backgroundScale = 4;
  const textWidth = text.getBoundingClientRect().width;
  const textHeight = text.getBoundingClientRect().height;
  const shineWidth = 48;
  const angle = 45 * Math.PI / 180;

  // NEW in Step 3: calculate how far the shine must travel outside the text.
  const safeDistance =
    (shineWidth + textHeight * Math.abs(Math.cos(angle)) / 2)
    / Math.abs(Math.sin(angle));

  // NEW in Step 3: use safeDistance to create safe start and end positions.
  const backgroundWidth = textWidth * backgroundScale;
  const travelWidth = backgroundWidth - textWidth;
  const startPosition = (backgroundWidth / 2 + safeDistance) / travelWidth;
  const endPosition =
    (backgroundWidth / 2 - textWidth - safeDistance) / travelWidth;

  root.style.setProperty("--start-position", `${startPosition * 100}%`);
  root.style.setProperty("--end-position", `${endPosition * 100}%`);
</script>

4. Animate background-position

The letters do not move. Only the oversized background moves behind the clipped text.

<!-- NEW in Step 4: combine the pieces in one document. -->
<main>
  <p class="shimmer">CSS is awesome, right?</p>
</main>

<style>
  /* NEW in Step 4: receive the safe positions calculated in Step 3. */
  :root {
    --start-position: 72%;
    --end-position: 28%;
  }

  /* NEW in Step 4: use a diagonal, narrow highlight. */
  .shimmer {
    color: transparent;
    background: linear-gradient(
      45deg,
      #8a8a8a calc(50% - 2ch),
      #ffffff 50%,
      #8a8a8a calc(50% + 2ch)
    );
    background-size: 400% 100%;
    background-clip: text;
    -webkit-background-clip: text;
    /* NEW in Step 4: animate the background, not the letters. */
    animation: shimmer 2.4s infinite ease-in-out;
  }

  /* NEW in Step 4: finish at the safe side with a smooth hold. */
  @keyframes shimmer {
    0% { background-position: var(--start-position) 0; }
    72%, 100% { background-position: var(--end-position) 0; }
  }
</style>