I was browsing my Codepen the other day and came across a pen I had made when @property
was first available to test but not well supported. Now that @property
is pretty well supported, animated CSS gradients are just... possible now. I threw together a few more ideas and then decided that a quick note of how to do this and some demos would make a great blog post.
How does this work?
It's as simple as registering any part of the gradient that you want to change, and then animating it with a transition or an animation.
@property --color-1 {
syntax: "<color>";
inherits: false;
initial-value: #009688;
}
@property --color-2 {
syntax: "<color>";
inherits: false;
initial-value: #00bcd4;
}
.gradient {
background: linear-gradient(in oklab to right, var(--color-1), var(--color-2));
animation: change-colors
}
@keyframes change-colors {
100% {
--color-1: #E91E63;
--color-2: #9c27b0;
}
}
For the purpose of the rest of the demos, I'm going to abstract the base styles into an .animated-gradient
class and declare the custom properties ahead of time, and then each individual demo will get an additional class and animation.
@property --color-1 {
syntax: "<color>";
inherits: false;
initial-value: #009688;
}
@property --color-2 {
syntax: "<color>";
inherits: false;
initial-value: #00bcd4;
}
@property --color-3 {
syntax: "<color>";
inherits: false;
initial-value: #E91E63;
}
@property --color-4 {
syntax: "<color>";
inherits: false;
initial-value: #9c27b0;
}
@property --color-5 {
syntax: "<color>";
inherits: false;
initial-value: #9c27b0;
}
@property --color-6 {
syntax: "<color>";
inherits: false;
initial-value: #9c27b0;
}
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0turn;
}
@property --stop-1 {
syntax: "<percentage>";
inherits: false;
initial-value: 50%;
}
.animated-gradient {
--direction: to bottom right;
--easing: ease;
--length: 10s;
width: 400px;
height: 200px;
background: linear-gradient(in oklab var(--direction), var(--color-1), var(--color-2));
animation: var(--animation) var(--length) var(--easing) infinite;
}
Demos
Color to color
.one {
--color-1: #009688;
--color-2: #00bcd4;
--animation: move;
}
@keyframes move {
50% {
--color-1: #E91E63;
--color-2: #9c27b0;
}
}
Side to side
.two {
--color-1: #009688;
--color-2: #00bcd4;
--animation: side;
--direction: to right;
--easing: linear;
}
@keyframes side {
0% {
--color-1: #009688;
--color-2: #00bcd4;
}
25% {
--color-1: #00bcd4;
--color-2: #E91E63;
}
50% {
--color-1: #E91E63;
--color-2: #9c27b0;
}
75% {
--color-1: #9c27b0;
--color-2: #009688;
}
100% {
--color-1: #009688;
--color-2: #00bcd4;
}
}
Pulse
.three {
--color-1: #009688;
--color-2: #9c27b0;
--animation: pulse;
--length: 2s;
--easing: ease-in-out;
background: radial-gradient(var(--color-1), var(--color-2));
}
@keyframes pulse {
50% {
--color-2: #009688;
}
}
Spin
.four {
--animation: spin;
--easing: linear;
--length: 2s;
background: linear-gradient(var(--angle), var(--color-1), var(--color-2) 50%, var(--color-3) 50%, var(--color-4));
}
@keyframes spin {
0% {
--angle: 0turn;
}
50% {
--color-1: var(--color-2);
--color-4: var(--color-3);
}
100% {
--angle: 1turn;
}
}
Moving stops
.five {
--animation: stops;
--length: 5s;
background: linear-gradient(to right, var(--color-1), var(--color-2) var(--stop-1), var(--color-3) var(--stop-1), var(--color-4));
}
@keyframes stops {
0% {
--stop-1: 100%;
}
100% {
--stop-1: 0%;
}
}
Multicolor cloud
.six {
--color-1: rgba(255, 0, 0, 0.8);
--color-2: rgba(255, 0, 0, 0);
--color-3: rgba(0, 255, 0, 0.8);
--color-4: rgba(0, 255, 0, 0);
--color-5: rgba(0, 0, 255, 0.8);
--color-6: rgba(0, 0, 255, 0);
--animation: mix-it-up;
background: linear-gradient(217deg, var(--color-1), var(--color-2) 70.71%), linear-gradient(127deg, var(--color-3), var(--color-4) 70.71%), linear-gradient(336deg, var(--color-5), var(--color-6) 70.71%);
}
@keyframes mix-it-up {
16.6% {
--color-2: rgba(255, 0, 0, 0.8);
--color-3: rgba(255, 0, 0, 0);
--color-4: rgba(0, 255, 0, 0.8);
--color-5: rgba(0, 255, 0, 0);
--color-6: rgba(0, 0, 255, 0.8);
--color-1: rgba(0, 0, 255, 0);
}
33.2% {
--color-3: rgba(255, 0, 0, 0.8);
--color-4: rgba(255, 0, 0, 0);
--color-5: rgba(0, 255, 0, 0.8);
--color-6: rgba(0, 255, 0, 0);
--color-1: rgba(0, 0, 255, 0.8);
--color-2: rgba(0, 0, 255, 0);
}
50% {
--color-4: rgba(255, 0, 0, 0.8);
--color-5: rgba(255, 0, 0, 0);
--color-6: rgba(0, 255, 0, 0.8);
--color-1: rgba(0, 255, 0, 0);
--color-2: rgba(0, 0, 255, 0.8);
--color-3: rgba(0, 0, 255, 0);
}
66.6% {
--color-5: rgba(255, 0, 0, 0.8);
--color-6: rgba(255, 0, 0, 0);
--color-1: rgba(0, 255, 0, 0.8);
--color-2: rgba(0, 255, 0, 0);
--color-3: rgba(0, 0, 255, 0.8);
--color-4: rgba(0, 0, 255, 0);
}
83.2% {
--color-6: rgba(255, 0, 0, 0.8);
--color-1: rgba(255, 0, 0, 0);
--color-2: rgba(0, 255, 0, 0.8);
--color-3: rgba(0, 255, 0, 0);
--color-4: rgba(0, 0, 255, 0.8);
--color-5: rgba(0, 0, 255, 0);
}
}
Stripes
.seven {
background: linear-gradient(90deg, transparent 50%, hsl(from var(--color-1) h s calc(l + 10) / alpha) 50%), linear-gradient(0deg, var(--color-1), var(--color-2));
background-size: 50px 50px, auto;
--animation: move;
}
@keyframes move {
50% {
--color-1: #E91E63;
--color-2: #9c27b0;
}
}
Checkers
.eight {
--animation: reverse-colors;
background: var(--color-2);
background-image: linear-gradient(45deg, var(--color-1) 25%, transparent 25%), linear-gradient(-45deg, var(--color-1) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--color-1) 75%), linear-gradient(-45deg, transparent 75%, var(--color-1) 75%);
background-size:40px 40px;
background-position: 0 0, 0 20px, 20px -20px, -20px 0px;
}
@keyframes reverse-colors {
50% {
--color-1: #00bcd4;
--color-2: #009688;
}
}
The possibilities are endless
Other than the star movement, all of this is just gradient animation.