25 CSS Text Animations 21 / 25
CSS Text Decryption Animation
A sophisticated matrix-style decryption where columns of binary rain resolve into readable text — more structured than scramble, more cinematic than typewriter.
The code
<div class="ta-21">
<div class="ta-21__stage">
<p class="ta-21__label">System Boot</p>
<div class="ta-21__output" id="ta-21-output" aria-label="CLASSIFIED DATA"></div>
<p class="ta-21__sub">binary rain columns · staggered column resolve · JS timer</p>
</div>
</div> <div class="ta-21">
<div class="ta-21__stage">
<p class="ta-21__label">System Boot</p>
<div class="ta-21__output" id="ta-21-output" aria-label="CLASSIFIED DATA"></div>
<p class="ta-21__sub">binary rain columns · staggered column resolve · JS timer</p>
</div>
</div>.ta-21, .ta-21 *, .ta-21 *::before, .ta-21 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-21 ::selection { background: #14532d; color: #bbf7d0; }
.ta-21 {
--bg: #010a04;
--rain: #15803d;
--bright: #4ade80;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'JetBrains Mono', 'Courier New', monospace;
}
.ta-21__stage { text-align: center; width: 100%; }
.ta-21__label {
font-size: 0.68rem;
color: var(--rain);
letter-spacing: 0.25em;
text-transform: uppercase;
margin-bottom: 0.6rem;
}
.ta-21__output {
font-size: clamp(1.6rem, 5vw, 2.8rem);
font-weight: 700;
letter-spacing: 0.15em;
display: flex;
justify-content: center;
gap: 0.05em;
min-height: 1.2em;
}
.ta-21__col {
display: inline-flex;
flex-direction: column;
align-items: center;
position: relative;
}
.ta-21__char {
display: inline-block;
color: var(--rain);
animation: ta-21-rain 0.08s linear infinite;
opacity: 0.7;
}
.ta-21__char.decoded {
color: var(--bright);
animation: ta-21-flash 0.3s ease-out forwards;
text-shadow: 0 0 12px var(--bright), 0 0 30px rgba(74,222,128,0.4);
}
.ta-21__sub {
font-size: 0.65rem;
color: #052010;
margin-top: 0.8rem;
letter-spacing: 0.08em;
}
@keyframes ta-21-rain {
0% { opacity: 0.4; }
50% { opacity: 1; }
100% { opacity: 0.4; }
}
@keyframes ta-21-flash {
0% { opacity: 0; transform: scaleY(1.3); }
60% { opacity: 1; transform: scaleY(1); }
100% { opacity: 1; transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
.ta-21__column, .ta-21__char { animation: none !important; transition: none; }
} .ta-21, .ta-21 *, .ta-21 *::before, .ta-21 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-21 ::selection { background: #14532d; color: #bbf7d0; }
.ta-21 {
--bg: #010a04;
--rain: #15803d;
--bright: #4ade80;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'JetBrains Mono', 'Courier New', monospace;
}
.ta-21__stage { text-align: center; width: 100%; }
.ta-21__label {
font-size: 0.68rem;
color: var(--rain);
letter-spacing: 0.25em;
text-transform: uppercase;
margin-bottom: 0.6rem;
}
.ta-21__output {
font-size: clamp(1.6rem, 5vw, 2.8rem);
font-weight: 700;
letter-spacing: 0.15em;
display: flex;
justify-content: center;
gap: 0.05em;
min-height: 1.2em;
}
.ta-21__col {
display: inline-flex;
flex-direction: column;
align-items: center;
position: relative;
}
.ta-21__char {
display: inline-block;
color: var(--rain);
animation: ta-21-rain 0.08s linear infinite;
opacity: 0.7;
}
.ta-21__char.decoded {
color: var(--bright);
animation: ta-21-flash 0.3s ease-out forwards;
text-shadow: 0 0 12px var(--bright), 0 0 30px rgba(74,222,128,0.4);
}
.ta-21__sub {
font-size: 0.65rem;
color: #052010;
margin-top: 0.8rem;
letter-spacing: 0.08em;
}
@keyframes ta-21-rain {
0% { opacity: 0.4; }
50% { opacity: 1; }
100% { opacity: 0.4; }
}
@keyframes ta-21-flash {
0% { opacity: 0; transform: scaleY(1.3); }
60% { opacity: 1; transform: scaleY(1); }
100% { opacity: 1; transform: scaleY(1); }
}
@media (prefers-reduced-motion: reduce) {
.ta-21__column, .ta-21__char { animation: none !important; transition: none; }
}(function() {
const el = document.getElementById('ta-21-output');
if (!el) return;
const finalText = 'CLASSIFIED DATA';
const bits = '01';
const columnDelay = 180;
function decode() {
el.innerHTML = '';
const intervals = [];
[...finalText].forEach((ch, i) => {
const charEl = document.createElement('span');
charEl.className = 'ta-21__char';
charEl.textContent = ch === ' ' ? ' ' : bits[Math.floor(Math.random() * 2)];
el.appendChild(charEl);
if (ch === ' ') return;
const iv = setInterval(() => {
charEl.textContent = bits[Math.floor(Math.random() * 2)];
}, 80);
intervals.push(iv);
setTimeout(() => {
clearInterval(iv);
charEl.textContent = ch;
charEl.classList.add('decoded');
}, 500 + i * columnDelay);
});
setTimeout(() => {
intervals.forEach(clearInterval);
setTimeout(decode, 2800);
}, 500 + finalText.length * columnDelay + 400);
}
decode();
})(); (function() {
const el = document.getElementById('ta-21-output');
if (!el) return;
const finalText = 'CLASSIFIED DATA';
const bits = '01';
const columnDelay = 180;
function decode() {
el.innerHTML = '';
const intervals = [];
[...finalText].forEach((ch, i) => {
const charEl = document.createElement('span');
charEl.className = 'ta-21__char';
charEl.textContent = ch === ' ' ? ' ' : bits[Math.floor(Math.random() * 2)];
el.appendChild(charEl);
if (ch === ' ') return;
const iv = setInterval(() => {
charEl.textContent = bits[Math.floor(Math.random() * 2)];
}, 80);
intervals.push(iv);
setTimeout(() => {
clearInterval(iv);
charEl.textContent = ch;
charEl.classList.add('decoded');
}, 500 + i * columnDelay);
});
setTimeout(() => {
intervals.forEach(clearInterval);
setTimeout(decode, 2800);
}, 500 + finalText.length * columnDelay + 400);
}
decode();
})();How this works
JavaScript creates a grid where each character position first displays rapidly cycling binary digits (0 and 1) styled as a falling column effect via CSS animation. After a staggered delay proportional to column position, the cycling stops and the final character resolves with a green flash transition. The binary rain uses a fast setInterval per column, independent timers that stop and lock when their column's time comes.
CSS handles all the visual styling: the binary characters use a green monospace font with a pulsing opacity animation to suggest a live data stream. The final resolved characters switch to a different, brighter class that amplifies the glow — making the transition from 'decrypting' to 'decoded' visually distinct. The column-by-column resolution creates a sweeping left-to-right decode front.
Customize
- Change the decode front direction from left-to-right to right-to-left by reversing the delay calculation:
(totalChars - i) * columnDelay. - Add a wipe cursor — a bright vertical bar that sweeps across as columns resolve — by positioning a CSS
::beforepseudo-element and translating it in sync with the decode front. - Mix in a random scatter pattern instead of linear sweep by using
Math.random() * maxDelayper column for a simultaneous burst decode. - Apply to a table of data values, decrypting each cell independently on different timelines for a data-visualisation reveal effect.
- Change the rain characters from binary to hexadecimal by updating the character pool to
'0123456789ABCDEF'for a network packet aesthetic.
Watch out for
- Running many independent
setIntervaltimers simultaneously is fine for short demos but doesn't scale to thousands of characters — batch updates using a single master interval instead. - The binary rain column needs a fixed character width container — use a monospaced font and set
min-widthon each character cell to prevent layout jitter as digits change width. - Always clear all column intervals when the animation completes or when the element is removed from the DOM to prevent memory leaks in single-page application contexts.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| All | All | All | All |
Uses only standard setInterval and DOM manipulation. Works in all JavaScript-enabled browsers with no compatibility concerns.