We’ll get there! (Click Run Pen)

Goal: Take a page from ordinary to extraordinary with CSS transitions!

Background: Marqeta’s new Single Sign-On portal is kinda like the front-gates to all Marqeta web properties. After building out all of the functionality we decided to add some personality — Disneyland front-gates aren’t boring, they let you know that your dreams are about to come true. We wanted to send the same kinda message.

Step 1 — Background color gradient animation

The background was originally Marqeta Purple —

But we also have this great turquoise in Marqeta’s color palate —

Let’s make it so the background transitions from Marqeta Purple to turquoise.

Give it a second, it changes slowly (subtly, classily)

body {

background: linear-gradient(309deg, #4b0e8d, #13133d, #791ddc, #06dfd9);

background-size: 800% 800%;

animation: GradientAnimation 47s ease infinite;

height: 100vh;

width: 100%;

overflow: none;

position: relative;

}

0% {

background-position: 0% 46%;

}

50% {

background-position: 100% 55%;

}

100% {

background-position: 0% 46%;

}

} @keyframes GradientAnimation {0% {background-position: 0% 46%;50% {background-position: 100% 55%;100% {background-position: 0% 46%;

Let’s dissect what is going on here.

First, we add a linear gradient that passes through the colors we are interested in and rotate it to get diagonal stripes.

background: linear-gradient(309deg, #4b0e8d, #13133d, #791ddc, #06dfd9);

Second, we make the linear gradient 8x larger than the element it is the background for.

background-size: 800% 800%;

Third, we add an animation to change the background-position . With this animation running our element basically acts as a window to the much larger background gradient. The end effect is a slowly changing background where the color updates wash over the page. Very pretty.

body {

background: linear-gradient(309deg, #4b0e8d, #13133d, #791ddc, #06dfd9);

background-size: 800% 800%;

animation: GradientAnimation 47s ease infinite;

}

0% {

background-position: 0% 46%;

}

50% {

background-position: 100% 55%;

}

100% {

background-position: 0% 46%;

}

} @keyframes GradientAnimation {0% {background-position: 0% 46%;50% {background-position: 100% 55%;100% {background-position: 0% 46%;

To be fair, a similar effect can be achieved by just animating the background color. But, if we do that then we lose the color update “wash”.

Step 2 — icon animation

The always amazing Marqeta design team created some icons for this project to hone in on what kind of magic is behind this particular “front-gate”. Let’s animate them!

/* 3 second budget */

.quality-icon-empower {

animation: EmpowerAnimation 3s ease 1;

} .quality-icon-performant {

animation: PerformantAnimation 3s ease 1;

} .quality-icon-targeted {

animation: TargetedAnimation 3s ease 1;

} .quality-icon-inspired {

animation: InspiredAnimation 3s ease 1;

}

0% {

left: 95px;

opacity: 0;

}

15% {

left: 95px;

opacity: 1;

}

75% {

left: 95px;

opacity: 1;

}

100% {

left: 206px;

opacity: 0;

}

} @keyframes EmpowerAnimation {0% {left: 95px;opacity: 0;15% {left: 95px;opacity: 1;75% {left: 95px;opacity: 1;100% {left: 206px;opacity: 0;

0% {

left: 169px;

opacity: 0;

}

15% {

opacity: 0;

}

30% {

opacity: 1;

}

75% {

left: 169px;

opacity: 1;

}

100% {

left: 206px;

opacity: 0;

}

} @keyframes PerformantAnimation {0% {left: 169px;opacity: 0;15% {opacity: 0;30% {opacity: 1;75% {left: 169px;opacity: 1;100% {left: 206px;opacity: 0;

0% {

right: 169px;

opacity: 0;

}

30% {

opacity: 0;

}

45% {

right: 169px;

opacity: 1;

}

75% {

right: 169px;

opacity: 1;

}

100% {

right: 206px;

opacity: 0;

}

} @keyframes TargetedAnimation {0% {right: 169px;opacity: 0;30% {opacity: 0;45% {right: 169px;opacity: 1;75% {right: 169px;opacity: 1;100% {right: 206px;opacity: 0;

0% {

right: 95px;

opacity: 0;

}

45% {

opacity: 0;

}

60% {

right: 95px;

opacity: 1;

}

75% {

right: 95px;

opacity: 1;

}

100% {

right: 206px;

opacity: 0;

}

} @keyframes InspiredAnimation {0% {right: 95px;opacity: 0;45% {opacity: 0;60% {right: 95px;opacity: 1;75% {right: 95px;opacity: 1;100% {right: 206px;opacity: 0;

We are animating two things for each icon here: opacity and position. We want the icons to fade in sequentially and once they have all faded in, combine together in the center.

The key here is to define separate animations for each element that will all run within the same time budget (in this case, 3 seconds). This allows us to cover all of the requirements:

Fade in sequentially — each element fades in for 15% of the time budget. The first from 0%-15%, second from 15%-30%, third from 30%-45%, forth from 45%-60%.

Pause to show icons — from 60%-75% all icons are faded in and the user gets a moment to check them out (450 milliseconds to be exact).

Combine icons — from 75%-100% each icon moves from it’s current location to a common center and fades to reveal the Marqeta Icon (next step).

The idea is that these values combine to form Marqeta, so let's build out the Marqeta logo!

Step 3 — drawing with SVG and CSS animations

.mq-logo-element.draw {

fill-opacity: 0;

stroke: white;

stroke-width: 0.8;

stroke-dasharray: 150;

stroke-dashoffset: 150;

stroke-opacity: 1;

} /* 1.5 second budget */

.logo-animation-m {

animation: DrawM 1.5s linear 1;

} .logo-animation-first-a {

animation: DrawFirstA 1.5s linear 1;

} .logo-animation-r {

animation: DrawR 1.5s linear 1;

} .logo-animation-q {

animation: DrawQ 1.5s linear 1;

} .logo-animation-e {

animation: DrawE 1.5s linear 1;

} .logo-animation-t {

animation: DrawT 1.5s linear 1;

} .logo-animation-second-a {

animation: DrawSecondA 1.5s linear 1;

}

0% {

stroke-dashoffset: 150;

}

/* no delay */

35% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawM {0% {stroke-dashoffset: 150;/* no delay */35% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 150ms delay */

10% {

stroke-dashoffset: 150;

}

45% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawFirstA {0% {stroke-dashoffset: 150;/* 150ms delay */10% {stroke-dashoffset: 150;45% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 300ms delay */

20% {

stroke-dashoffset: 150;

}

55% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawR {0% {stroke-dashoffset: 150;/* 300ms delay */20% {stroke-dashoffset: 150;55% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 450ms delay */

30% {

stroke-dashoffset: 150;

}

65% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawQ {0% {stroke-dashoffset: 150;/* 450ms delay */30% {stroke-dashoffset: 150;65% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 600ms delay */

40% {

stroke-dashoffset: 150;

}

75% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawE {0% {stroke-dashoffset: 150;/* 600ms delay */40% {stroke-dashoffset: 150;75% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 750ms delay */

50% {

stroke-dashoffset: 150;

}

85% {

stroke-dashoffset: 0;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawT {0% {stroke-dashoffset: 150;/* 750ms delay */50% {stroke-dashoffset: 150;85% {stroke-dashoffset: 0;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

0% {

stroke-dashoffset: 150;

}

/* 900ms delay */

60% {

stroke-dashoffset: 150;

}

80% {

stroke-opacity: 1;

}

100% {

stroke-opacity: 0;

stroke-dashoffset: 0;

}

} @keyframes DrawSecondA {0% {stroke-dashoffset: 150;/* 900ms delay */60% {stroke-dashoffset: 150;80% {stroke-opacity: 1;100% {stroke-opacity: 0;stroke-dashoffset: 0;

Fancy! How does it work?

To “draw” the logo we are using stroke-dasharray and stroke-dashoffset 2 CSS properties that are available to the SVG <polyline> element.

stroke-dasharray allows you to define a pattern of dashes and gaps used to paint the outline of the shape.

stroke-dashoffset will offset the beginning of the dash array in your shape.

So, what if we were to define a stroke-dasharray with dashes that were so long that one dash was longer than the entire outline of your shape? And, what if you were to set stroke-dashoffset to the same length as that super-long dash you defined in stroke-dasharray ? The result would be very boring. You wouldn’t see anything because the offset would push the first dash in the stroke-dasharray beyond the outline of your entire shape.

Things get interesting when you change the stroke-dashoffet from the length of your obnoxiously long dash to 0. If you move the stroke-dashoffset to 0 you will see your shape, which at this point can be thought of as a section of one very long dash.

The key to the SVG draw effect is animating the transition from stroke-dashoffset being equal to your stroke-dasharry value (in this case 150) to 0. The animation will display intermediate stroke-dashoffset values on the journey from 150 to 0 and this will look like a single line is being drawn.

In order to draw the MARQETA letters in sequence, we use the same technique that we used for the icon animations — defining different animations for each element with the same time budget, this time 1.5 seconds. The technique for setting the delay is to “hold on” to stroke-dashoffset: 150; for a certain percentage of the 1.5 seconds budget. Here we sequentially add 10% of the time budget to every new letter.

At the end of this animation, the MARQETA logos are filled in. This is accomplished by a fill-opacity animation that starts at 80% of the 1.5-second budget:

.mq-logo-element.fill {

fill-opacity: 0;

animation: Fill 1.5s ease 1;

animation-fill-mode: forwards;

}

0% {

fill-opacity: 0;

}

80% {

fill-opacity: 0;

}

100% {

fill-opacity: 1;

}

} @keyframes Fill {0% {fill-opacity: 0;80% {fill-opacity: 0;100% {fill-opacity: 1;

animation-fill-mode: forwards; makes it so fill-opacity: 1; will hold after the animation is done. More details on animation-fill-mode here: https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode

Lets put it all together!

Remember me?

Back to time budgets! To build out the full animation we orchestrate the Icon Combine animation and the Logo Draw animation using animation-delay and a wrapping animation, AnimateLogo. Our total budget here is 4.7 seconds. Let’s look at how we use it.

.logo-animation-mq-logo-wrap {

animation: AnimateLogo 4.7s ease 1;

}

0% {

opacity: 0;

left: 181px;

}

/* 2.5 second mark (2500 / 4700 = 0.5319) */

53.19% {

opacity: 0;

}

/* 3 second mark (3000 / 4700 = 0.6382) */

63.82% {

opacity: 1;

left: 181px;

}

/* 3.2 second mark (3200 / 4700 = 0.6808) */

68.08% {

left: 117px;

}

100% {

opacity: 1;

}

} @keyframes AnimateLogo {0% {opacity: 0;left: 181px;/* 2.5 second mark (2500 / 4700 = 0.5319) */53.19% {opacity: 0;/* 3 second mark (3000 / 4700 = 0.6382) */63.82% {opacity: 1;left: 181px;/* 3.2 second mark (3200 / 4700 = 0.6808) */68.08% {left: 117px;100% {opacity: 1;

0 seconds — Start Icon Combine animation

2.5 seconds — Start to fade in the Logo (letters are still hidden)

3.0 seconds — End Icon Combine animation. Start to move logo in place for Logo Draw animation

3.2 seconds — End moving logo in place for Logo Draw animation. Start Logo Draw animation.

/* we use animation-delay: 3.2s; to start the 1.5 second Logo Draw budget 3.2 seconds into the full 4.7 animation budget */ .logo-animation-m {

animation: DrawM 1.5s linear 1;

animation-delay: 3.2s;

}

4.7 seconds — End Logo Draw animation

And that should do it!

Closing thoughts

I hope this article has helped you get a better sense of how to use the tools available to you in CSS animations to make something ordinary, like a background or an SVG, into something extraordinary!

Also, a quick shout out to Marqeta for building out a strong dev culture and supporting this as part of the effort to build out Single Sign-On! Come be a part of this culture — join us as we continue to grow!