Here’s a rewritten version of the article that maintains the original intent and technical details, while improving clarity and flow:

Planet and stars illustration

Recently, I gave my portfolio a fresh update over at johnrhea.com. (If your team needs a CSS or front-end engineer who’s handy with animation and storytelling, I’m your guy.) I decided to reuse a planetary design I’d built for a previous side project, including an orbiting moon animation I originally created back in 2019.

To save time, I dropped the old animation into the new site, updating the units from ems to viewport units (with some math I was admittedly proud of) so it would scale responsively. But on mobile, the planet shifted slightly up and down as the moon orbited. I initially blamed the animation (spoiler: it wasn’t the cause), but the process led to a better-optimized animation—and this article.

The Original Animation

The original moon animation spanned 60 seconds and clocked in at a hefty 141 lines of CSS. Here’s a snippet of what that looked like:

View code (Original 141-line animation)


If you look closely, you’ll notice that the keyframes from 0% to 20% repeat every 20% interval all the way to 100%. In hindsight, I could’ve just looped a single 20% sequence instead of repeating it five times. That realization alone allowed me to reduce the animation duration to 12 seconds and cut the code down to just 36 lines.

Here’s the simplified version:

#moon1 {
  animation: moon-one 12s infinite;
}

@keyframes moon-one {
  0% {
    transform: translate(0, 0) scale(1);
    z-index: 2;
    animation-timing-function: ease-in;
  }
  5% {
    transform: translate(-3.51217391vw, 3.50608696vw) scale(1.5);
    z-index: 2;
    animation-timing-function: ease-out;
  }
  9.9% {
    z-index: 2;
  }
  10% {
    transform: translate(-5.01043478vw, 6.511304348vw) scale(1);
    z-index: -1;
    animation-timing-function: ease-in;
  }
  15% {
    transform: translate(1.003478261vw, 2.50608696vw) scale(0.25);
    z-index: -1;
    animation-timing-function: ease-out;
  }
  19.9% {
    z-index: -1;
  }
  20% {
    transform: translate(0, 0) scale(1);
    z-index: 2;
    animation-timing-function: ease-in;
  }
}

Recognizing Repetitive Patterns

With the code trimmed, I noticed the five keyframes could be evenly distributed across 0% to 100%. I mapped 0% → 0%, 5% → 25%, 10% → 50%, 15% → 75%, and 20% → 100%. This allowed me to remove redundant keyframes and adjust the z-index markers accordingly:

#moon1 {
  animation: moon-one 12s infinite;
}

@keyframes moon-one {
  0%, 100% {
    transform: translate(0, 0) scale(1);
    z-index: 2;
    animation-timing-function: ease-in;
  }
  25% {
    transform: translate(-3.5vw, 3.5vw) scale(1.5);
    animation-timing-function: ease-out;
  }
  49.9% {
    z-index: 2;
  }
  50% {
    transform: translate(-5vw, 6.5vw) scale(1);
    z-index: -1;
    animation-timing-function: ease-in;
  }
  75% {
    transform: translate(1vw, 2.5vw) scale(0.25);
    animation-timing-function: ease-out;
  }
  99.9% {
    z-index: -1;
  }
}

Cleaning Up the Numbers

Let’s be honest—values like -3.51217391vw are overkill. On a 1000px-wide screen, that’s 35.1217391px—way too precise. I rounded everything to the


Discover more from WIREDGORILLA

Subscribe to get the latest posts sent to your email.

Similar Posts