16 CSS Gradient Animations 02 / 16
CSS Diagonal Shifting Linear Gradient
A continuously sweeping diagonal linear gradient background that animates its background-position across a 300% × 300% canvas to simulate uninterrupted color flow across the full page.
The code
<div class="ga-02">
<div class="ga-02__content">
<div class="ga-02__eyebrow">
<div class="ga-02__dot"></div>
<span class="ga-02__eyebrow-text">Live Platform</span>
</div>
<h1 class="ga-02__title">Design Without<br>Limits</h1>
<p class="ga-02__sub">A professional-grade design suite that moves as fast as your ideas. Collaborate, prototype, and ship — all from one place.</p>
<div class="ga-02__stats">
<div class="ga-02__stat">
<span class="ga-02__stat-num">4M+</span>
<span class="ga-02__stat-label">Creators</span>
</div>
<div class="ga-02__stat">
<span class="ga-02__stat-num">98%</span>
<span class="ga-02__stat-label">Uptime</span>
</div>
<div class="ga-02__stat">
<span class="ga-02__stat-num">12ms</span>
<span class="ga-02__stat-label">Latency</span>
</div>
</div>
</div>
<div class="ga-02__controls">
<span class="ga-02__ctrl-label">Angle:</span>
<button class="ga-02__angle-btn" data-angle="45deg">45°</button>
<button class="ga-02__angle-btn active" data-angle="135deg">135°</button>
<button class="ga-02__angle-btn" data-angle="180deg">180°</button>
<button class="ga-02__angle-btn" data-angle="90deg">90°</button>
</div>
</div> <div class="ga-02">
<div class="ga-02__content">
<div class="ga-02__eyebrow">
<div class="ga-02__dot"></div>
<span class="ga-02__eyebrow-text">Live Platform</span>
</div>
<h1 class="ga-02__title">Design Without<br>Limits</h1>
<p class="ga-02__sub">A professional-grade design suite that moves as fast as your ideas. Collaborate, prototype, and ship — all from one place.</p>
<div class="ga-02__stats">
<div class="ga-02__stat">
<span class="ga-02__stat-num">4M+</span>
<span class="ga-02__stat-label">Creators</span>
</div>
<div class="ga-02__stat">
<span class="ga-02__stat-num">98%</span>
<span class="ga-02__stat-label">Uptime</span>
</div>
<div class="ga-02__stat">
<span class="ga-02__stat-num">12ms</span>
<span class="ga-02__stat-label">Latency</span>
</div>
</div>
</div>
<div class="ga-02__controls">
<span class="ga-02__ctrl-label">Angle:</span>
<button class="ga-02__angle-btn" data-angle="45deg">45°</button>
<button class="ga-02__angle-btn active" data-angle="135deg">135°</button>
<button class="ga-02__angle-btn" data-angle="180deg">180°</button>
<button class="ga-02__angle-btn" data-angle="90deg">90°</button>
</div>
</div>.ga-02, .ga-02 *, .ga-02 *::before, .ga-02 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-02 ::selection { background: rgba(236,72,153,.4); color: #fff; }
.ga-02 {
--dur: 8s;
--angle: 135deg;
--c1: #1e0533;
--c2: #312e81;
--c3: #0c4a6e;
--c4: #831843;
--c5: #1e0533;
position: relative;
width: 100%;
min-height: 100vh;
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 40px;
padding: 48px 24px;
}
/* The animated background sits on a pseudo-element so background-size can be animated */
.ga-02::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(var(--angle),
var(--c1) 0%,
var(--c2) 20%,
var(--c3) 40%,
var(--c4) 60%,
var(--c2) 80%,
var(--c5) 100%
);
background-size: 300% 300%;
animation: ga-02-sweep var(--dur) ease-in-out infinite;
}
@keyframes ga-02-sweep {
0% { background-position: 0% 0%; }
25% { background-position: 100% 0%; }
50% { background-position: 100% 100%; }
75% { background-position: 0% 100%; }
100% { background-position: 0% 0%; }
}
/* Noise overlay for depth */
.ga-02::after {
content: '';
position: absolute;
inset: 0;
opacity: .04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 200px 200px;
pointer-events: none;
}
/* Content */
.ga-02__content {
position: relative;
z-index: 2;
text-align: center;
max-width: 680px;
}
.ga-02__eyebrow {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 20px;
}
.ga-02__dot {
width: 8px; height: 8px;
border-radius: 50%;
background: #f472b6;
animation: ga-02-pulse-dot 2s ease-in-out infinite;
}
@keyframes ga-02-pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: .5; transform: scale(.7); }
}
.ga-02__eyebrow-text {
color: #f9a8d4;
font-size: 12px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
}
.ga-02__title {
font-size: clamp(2.2rem, 5.5vw, 3.6rem);
font-weight: 900;
line-height: 1.08;
letter-spacing: -.03em;
color: #fff;
margin-bottom: 18px;
text-shadow: 0 2px 30px rgba(0,0,0,.4);
}
.ga-02__sub {
font-size: 1.05rem;
color: rgba(255,255,255,.65);
line-height: 1.7;
margin-bottom: 36px;
}
.ga-02__stats {
display: flex;
gap: 0;
justify-content: center;
border: 1px solid rgba(255,255,255,.1);
border-radius: 14px;
overflow: hidden;
backdrop-filter: blur(12px);
background: rgba(0,0,0,.2);
max-width: 460px;
margin: 0 auto;
}
.ga-02__stat {
flex: 1;
padding: 16px 8px;
text-align: center;
border-right: 1px solid rgba(255,255,255,.1);
}
.ga-02__stat:last-child { border-right: none; }
.ga-02__stat-num {
font-size: 1.5rem;
font-weight: 800;
color: #fff;
display: block;
}
.ga-02__stat-label {
font-size: .7rem;
font-weight: 600;
color: rgba(255,255,255,.45);
text-transform: uppercase;
letter-spacing: .07em;
margin-top: 2px;
}
/* Angle control */
.ga-02__controls {
position: relative;
z-index: 2;
display: flex;
align-items: center;
gap: 10px;
}
.ga-02__ctrl-label {
font-size: 11px;
font-weight: 600;
color: rgba(255,255,255,.4);
text-transform: uppercase;
letter-spacing: .08em;
}
.ga-02__angle-btn {
padding: 5px 12px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
cursor: pointer;
border: 1px solid rgba(255,255,255,.15);
background: rgba(255,255,255,.07);
color: rgba(255,255,255,.5);
transition: all .2s;
}
.ga-02__angle-btn.active,
.ga-02__angle-btn:hover {
background: rgba(244,114,182,.2);
border-color: rgba(244,114,182,.5);
color: #f9a8d4;
}
@media (prefers-reduced-motion: reduce) {
.ga-02::before { animation: none; background-position: 50% 50%; }
.ga-02__dot { animation: none; }
} .ga-02, .ga-02 *, .ga-02 *::before, .ga-02 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-02 ::selection { background: rgba(236,72,153,.4); color: #fff; }
.ga-02 {
--dur: 8s;
--angle: 135deg;
--c1: #1e0533;
--c2: #312e81;
--c3: #0c4a6e;
--c4: #831843;
--c5: #1e0533;
position: relative;
width: 100%;
min-height: 100vh;
overflow: hidden;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 40px;
padding: 48px 24px;
}
/* The animated background sits on a pseudo-element so background-size can be animated */
.ga-02::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(var(--angle),
var(--c1) 0%,
var(--c2) 20%,
var(--c3) 40%,
var(--c4) 60%,
var(--c2) 80%,
var(--c5) 100%
);
background-size: 300% 300%;
animation: ga-02-sweep var(--dur) ease-in-out infinite;
}
@keyframes ga-02-sweep {
0% { background-position: 0% 0%; }
25% { background-position: 100% 0%; }
50% { background-position: 100% 100%; }
75% { background-position: 0% 100%; }
100% { background-position: 0% 0%; }
}
/* Noise overlay for depth */
.ga-02::after {
content: '';
position: absolute;
inset: 0;
opacity: .04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 200px 200px;
pointer-events: none;
}
/* Content */
.ga-02__content {
position: relative;
z-index: 2;
text-align: center;
max-width: 680px;
}
.ga-02__eyebrow {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 20px;
}
.ga-02__dot {
width: 8px; height: 8px;
border-radius: 50%;
background: #f472b6;
animation: ga-02-pulse-dot 2s ease-in-out infinite;
}
@keyframes ga-02-pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: .5; transform: scale(.7); }
}
.ga-02__eyebrow-text {
color: #f9a8d4;
font-size: 12px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
}
.ga-02__title {
font-size: clamp(2.2rem, 5.5vw, 3.6rem);
font-weight: 900;
line-height: 1.08;
letter-spacing: -.03em;
color: #fff;
margin-bottom: 18px;
text-shadow: 0 2px 30px rgba(0,0,0,.4);
}
.ga-02__sub {
font-size: 1.05rem;
color: rgba(255,255,255,.65);
line-height: 1.7;
margin-bottom: 36px;
}
.ga-02__stats {
display: flex;
gap: 0;
justify-content: center;
border: 1px solid rgba(255,255,255,.1);
border-radius: 14px;
overflow: hidden;
backdrop-filter: blur(12px);
background: rgba(0,0,0,.2);
max-width: 460px;
margin: 0 auto;
}
.ga-02__stat {
flex: 1;
padding: 16px 8px;
text-align: center;
border-right: 1px solid rgba(255,255,255,.1);
}
.ga-02__stat:last-child { border-right: none; }
.ga-02__stat-num {
font-size: 1.5rem;
font-weight: 800;
color: #fff;
display: block;
}
.ga-02__stat-label {
font-size: .7rem;
font-weight: 600;
color: rgba(255,255,255,.45);
text-transform: uppercase;
letter-spacing: .07em;
margin-top: 2px;
}
/* Angle control */
.ga-02__controls {
position: relative;
z-index: 2;
display: flex;
align-items: center;
gap: 10px;
}
.ga-02__ctrl-label {
font-size: 11px;
font-weight: 600;
color: rgba(255,255,255,.4);
text-transform: uppercase;
letter-spacing: .08em;
}
.ga-02__angle-btn {
padding: 5px 12px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
cursor: pointer;
border: 1px solid rgba(255,255,255,.15);
background: rgba(255,255,255,.07);
color: rgba(255,255,255,.5);
transition: all .2s;
}
.ga-02__angle-btn.active,
.ga-02__angle-btn:hover {
background: rgba(244,114,182,.2);
border-color: rgba(244,114,182,.5);
color: #f9a8d4;
}
@media (prefers-reduced-motion: reduce) {
.ga-02::before { animation: none; background-position: 50% 50%; }
.ga-02__dot { animation: none; }
}(function() {
const wrapper = document.querySelector('.ga-02');
const btns = wrapper.querySelectorAll('.ga-02__angle-btn');
btns.forEach(btn => {
btn.addEventListener('click', () => {
btns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
wrapper.style.setProperty('--angle', btn.dataset.angle);
});
});
})(); (function() {
const wrapper = document.querySelector('.ga-02');
const btns = wrapper.querySelectorAll('.ga-02__angle-btn');
btns.forEach(btn => {
btn.addEventListener('click', () => {
btns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
wrapper.style.setProperty('--angle', btn.dataset.angle);
});
});
})();How this works
The technique creates the illusion of a moving gradient without ever changing gradient stop values. A linear-gradient with five deep purple-navy-indigo stops is applied to the ::before pseudo-element with background-size: 300% 300% — making the gradient canvas three times larger than the visible area in both dimensions. The @keyframes ga-02-sweep then walks background-position through all four corners (0% 0% → 100% 0% → 100% 100% → 0% 100% → 0% 0%) over 8 seconds on an ease-in-out curve, creating the sweeping diagonal wave of colour.
Only background-position is animated — a compositable paint-only property on most browsers — keeping INP and CLS scores clean. An SVG feTurbulence noise layer (via an inline data URI on ::after) at 4% opacity adds micro-texture so the gradient reads as premium rather than flat. The --angle CSS variable lets the JS controls hot-swap the gradient direction without touching the keyframe, giving interactive angle selection at near-zero cost.
Customize
- Shift the colour story by replacing
--c2: #312e81(indigo) and--c3: #0c4a6e(sky) — the five-stop structure means you can add a midpoint accent like#7c3aedat the 50% position. - Speed up or slow down the sweep by changing
--duron.ga-02— try4sfor an energetic brand feel or20sfor an ambient atmospheric effect. - Change the sweep path from a loop to an alternating back-and-forth by replacing
animation: ga-02-sweep ... infinitewithanimation: ga-02-sweep ... infinite alternate. - Increase the canvas size from
background-size: 300% 300%to400% 400%for more dramatic travel distance between colours during a single cycle. - Stack a second
linear-gradientlayer (withbackground-blend-mode: overlay) on the same pseudo-element to add a cross-axis highlight stripe over the primary sweep.
Watch out for
background-positionanimation on large backgrounds can trigger raster uploads on the GPU; keep the pseudo-element atposition: absolute; inset: 0so it stays in its own composited layer.- The inline SVG noise data-URI contains percent-encoded characters — some build tools or CSS minifiers may double-encode the
%25sequences, turning the noise invisible; test post-build. - Safari prior to 15.4 paints
::before/::afterbackgrounds on the CPU when the parent is a flex or grid container withoverflow: hidden— addwill-change: background-positionas a workaround on those targets.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 9.1+ | 36+ | 49+ |
background-position animation of percentage values on oversized backgrounds is universally supported; the SVG noise data-URI requires inline SVG support (all modern browsers).