Recreating the Apple Music Hits Playlist Animation in CSS

Avatar of Geoff Graham
Geoff Graham on

Try Mailchimp today. MailChimp Tracking Pixel

Apple Music has this “Spatial Audio” feature where the direction of the music in your headphones is based on the location of the device. It’s tough to explain just how neat it is. But that’s not what I’m here to talk about.

I opened up the Apple Music app and saw a featured playlist of hit songs that support Spatial Audio. The cover for it is this brightly-colored pink container that holds a bunch of boxes stacked one on top of another. The boxes animate in one at a time, fading in at the center of the container, then fading out as it scales to the size of the container. Like an infinite loop.

Animated GIF showing the Apple Music UI we are recreating. It's brightly colored shades of pink against a dark gray background with information about the playlist to the right of the pattern, and options to play and shuffle the sings in orange buttons.

Cool! I knew I had to re-create it in CSS. So I did.

Here’s how it works…

The markup

I started with the HTML. There’s obviously a container we need to define, plus however many boxes we want to animate. I went with an even 10 boxes in the container.

<div class="container">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <!-- etc. -->
</div>

That’s literally it for HTML. We are free to jump right into the CSS!

Styling the container

Nothing too fancy here. I measured approximate dimensions based on what I saw in Apple Music, which happened to be 315px × 385px. then I took a screenshot of the cover and dropped it into my image editing app to get the lightest possible color, which is around the outside edges of the container. My color picker landed on #eb5bec.

.container {
  background-color: #eb5bec;
  height: 315px;
  width: 385px;
}

As I was doing this, I knew I would probably want this to be a grid container to align the boxes and any other elements in the center. I also figured that the boxes themselves would start from the center of the container and stack on top of one another, meaning there will be some absolute positioning. That also means the container ought to have relative positioning to reign them in.

.container {
  background-color: #eb5bec;
  height: 315px;
  position: relative;
  width: 385px;
}

And since we want the boxes to start from the center, we can reach for grid to help with that:

.container {
  background-color: #eb5bec;
  display: grid;
  height: 315px;
  place-items: center;
  position: relative;
  width: 385px;
}

If the boxes in the container are growing outward, then there’s a chance that they could expand beyond the container. Better hide any possible overflow.

.container {
  background-color: #eb5bec;
  height: 315px;
  overflow: hidden;
  position: relative;
  width: 385px;
}

I also noticed some rounded corners on it, so let’s drop that in while we’re here.

.container {
  background-color: #eb5bec;
  border-radius: 16px;
  height: 315px;
  position: relative;
  width: 385px;
}

So far, so good!

Styling the boxes

We have 10 .box elements in the markup and we want them stacked on top of one another. I started with some absolute positioning, then sized them at 100px square. Then I did the same thing with my image editing app to find the darkest color value of a box, which was #471e45.

.box {
  background: #471e45;
  height: 100px;
  position: absolute;
  width: 100px;
}

The boxes seem to fade out as they grow. That allows one box to be seen through the other, so let’s make them opaque to start.

.box {
  background: #471e45;
  height: 100px;
  opacity: 0.5;
  position: absolute;
  width: 100px;
}

Cool, cool. We’re unable to see all the boxes as they’re stacked on top of one another, but we’re making progress!

Creating the animation

Ready to write some @keyframes? We’re gonna make this super simple, going from 0 to 100% without any steps in between. We don’t even need those percentages!

@keyframes grow {
  from {
    /* do stuff */
  }
  to {
    /* do stuff */
  }
}

Specifically, we want two things to happen from start to finish:

  • The boxes go from our starting opacity value of 0.5 to 0 (fully transparent).
  • The boxes scale up to the edges of the container.
@keyframes grow {
  from {
    opacity: 0.5;
    transform: scale(0);
  }
  to {
    opacity: 0;
    transform: scale(3.85);
  }
}

How’d I land on scaling the boxes up by 3.85? Our boxes are 100px square and the container is 385px tall. A value of 3.85 gets the boxes up to 385px as they fade completely out which makes for a nice linear animation when we get there.

Speaking of which…

Applying the animation

It’s pretty easy to call the animation on our boxes. Just gotta make sure it moves in a liner timing function on an infinite basis so it’s like the Energizer Bunny and keeps going and going and going and going and…

.box {
  animation: grow 10s linear infinite; /* 10s = 10 boxes */
  /* etc. */
}

This gives us the animation we want. But! The boxes are all moving at the same time, so all we see is one giant box growing.

We’ve gotta stagger those little fellers. No loops in vanilla CSS, unfortunately, so we have to delay each box individually. We can start by setting a custom property for the delay, set it to one second, then redefine the custom property on each instance.

.box {
  --delay: 1s;
  
  animation-delay: var(--delay);
  /* same as before */
}
.box:nth-child(2) {
  --delay: 2s;
}
.box:nth-child(3) {
  --delay: 3s;
}
.box:nth-child(4) {
  --delay: 4s;
}
.box:nth-child(5) {
  --delay: 5s;
}
/* five more times... */

Huzzah!

Keep on rockin’

That’s it! We just recreated the same sort of effect used by Apple Music. There are a few finishing touches we could plop in there, like the content and whatnot. Here’s my final version again: