Visual Effects
Distortion, holographic, CRT, VHS, glitch and retro digital.
Updated
How are glitch, VHS and distortion effects made on the web?
Distortion effects deliberately corrupt an image or a title to create a retro or digital mood. Glitches use offset copies cut with clip-path and tinted with text-shadow, CRT and VHS looks add scanlines and noise layers, and true warping uses SVG filters such as feTurbulence with feDisplacementMap, or WebGL.
How to choose, performance and accessibility
- Reserve them for moments that carry the brand, such as a music or gaming landing page, a 404 page or a hero title, with RGB Split, VHS Effect or CRT Screen, and keep body text clean.
- Fast flicker can cause discomfort: limit intensity, never flash more than three times per second, and turn the animation off for
prefers-reduced-motion. - RGB Split, Holographic Card, CRT Monitor Effect and Synthwave Scene are free with their code; the other effects come with Premium.
RGB Split
Chromatic separation with a corruption style.
View source code
<div class="demo-preview">
<div class="rgb-split" data-text="GLITCH">GLITCH</div>
</div>.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
box-sizing: border-box
}
.demo-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 280px;
overflow: hidden;
position: relative;
background: rgb(10, 10, 15);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.rgb-split {
font-size: 3rem;
font-weight: 900;
position: relative;
z-index: 0;
color: white;
cursor: pointer;
}
.rgb-split::before,
.rgb-split::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.rgb-split::before {
color: #ff0000;
z-index: -1;
animation: rgbShiftR 0.15s infinite;
}
.rgb-split::after {
color: #00ffff;
z-index: -1;
animation: rgbShiftC 0.15s infinite;
}
@keyframes rgbShiftR {
0%,
100% {
transform: translate(-3px, 0);
}
50% {
transform: translate(-5px, 2px);
}
}
@keyframes rgbShiftC {
0%,
100% {
transform: translate(3px, 0);
}
50% {
transform: translate(5px, -2px);
}
}/* Pure CSS — no JavaScript required */VHS Effect
Retro VHS cassette effect with scanlines.
Pixelate Effect
8-bit style pixelation on hover.
Melting Text
Text that melts and deforms.
Wave Distortion
Text wave and twist distortion.
Noise Overlay
Animated film-style grain texture.
CRT Screen
Retro cathode ray tube screen effect.
Glitch Blocks
Random corruption blocks.
Broken Glass
Broken glass effect with shards and cracks.
Static Noise
Analog-style static TV interference.
Holographic Card
Rainbow reflections on hover, Pokemon card style.
View source code
<div class="demo-preview">
<div class="holo-card">HOLO CARD</div>
</div>.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
box-sizing: border-box
}
.demo-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 280px;
overflow: hidden;
position: relative;
background: rgb(10, 10, 15);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.holo-card {
width: 280px;
height: 180px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 16px;
position: relative;
overflow: hidden;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.25rem;
}
.holo-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(125deg,
transparent 0%,
rgba(255, 0, 128, 0.3) 15%,
rgba(0, 255, 255, 0.3) 30%,
rgba(255, 255, 0, 0.3) 45%,
rgba(0, 255, 128, 0.3) 60%,
rgba(128, 0, 255, 0.3) 75%,
transparent 100%);
background-size: 200% 200%;
opacity: 0;
transition: opacity 0.3s;
mix-blend-mode: color-dodge;
}
.holo-card::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(0deg,
transparent,
transparent 2px,
rgba(255, 255, 255, 0.03) 2px,
rgba(255, 255, 255, 0.03) 4px);
}
.holo-card:hover::before {
opacity: 1;
animation: holoShift 3s linear infinite;
}
@keyframes holoShift {
0% {
background-position: 0% 0%;
}
100% {
background-position: 200% 200%;
}
}/* Pure CSS — no JavaScript required */Iridescent Surface
Shifting surface like mother of pearl or soap bubble.
Prismatic Text
Text with chromatic aberration and dispersion.
Chrome Reflection
Chrome metallic surface with reflection.
Oil Slick
Oil puddle effect with multicolor reflections.
Hologram Projection
Floating holographic projection with scanlines.
Rainbow Refraction
Light refracted through a prism.
Crystal Effect
Crystal gem with internal reflections.
Holographic Badge
Sticker badge with rotating holographic foil effect.
Laser Beam
Animated laser beam with rainbow refraction.
> LOADING DATA...
> READY_
CRT Monitor Effect
CRT monitor effect with scanlines, screen curvature and green phosphor glow.
View source code
<div class="demo-preview">
<div class="crt-monitor">
<div class="crt-content">
<span>> SYSTEM BOOT OK</span><br>
<span>> LOADING DATA...</span><br>
<span>> READY_</span>
</div>
</div>
</div>.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
box-sizing: border-box
}
.demo-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 280px;
overflow: hidden;
position: relative;
background: rgb(10, 10, 15);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.crt-monitor {
background: #0a0a0a;
border: 8px solid #2a2a2a;
border-radius: 20px;
padding: 24px;
position: relative;
overflow: hidden;
width: 280px;
min-height: 160px;
box-shadow: inset 0 0 60px rgba(0, 255, 0, 0.1), 0 0 20px rgba(0, 0, 0, 0.5);
}
.crt-monitor::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.15) 2px,
rgba(0, 0, 0, 0.15) 4px);
pointer-events: none;
z-index: 2;
}
.crt-monitor::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, transparent 60%, rgba(0, 0, 0, 0.5) 100%);
pointer-events: none;
z-index: 3;
}
.crt-content {
color: #33ff33;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
text-shadow: 0 0 8px rgba(51, 255, 51, 0.6);
position: relative;
z-index: 1;
}
.crt-content span {
animation: crtFlicker 4s infinite;
}
@keyframes crtFlicker {
0%,
100% {
opacity: 1;
}
92% {
opacity: 1;
}
93% {
opacity: 0.7;
}
94% {
opacity: 1;
}
96% {
opacity: 0.8;
}
97% {
opacity: 1;
}
}/* Pure CSS — no JavaScript required */VHS Tracking
Animated VHS tracking distortion with horizontal displacement and color bleeding.
ASCII Art Renderer
Text displayed in ASCII characters with monospace font and green-on-black terminal look.
Pixel Dissolve
Element that pixelates and dissolves on hover with stepped CSS transitions.
LED Matrix Display
Text rendered as LED dot-matrix display with red glow and dot grid.
Retro Terminal
Green-on-black terminal with typing animation, blinking cursor and CRT scanlines.
Gameboy Palette
Content rendered in the Gameboy's 4-shade green palette with CSS filter.
Dot Matrix Printer
Text appearing line by line as if printed by a dot matrix printer.
Windows 95 UI Kit
Buttons and window with authentic Win95-style 3D beveled borders.
Synthwave Scene
Animated retro scene with perspective grid, neon sun and pink/cyan/purple palette.
View source code
<div class="demo-preview">
<div class="synthwave-scene">
<div class="synthwave-stars">
<span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span>
</div>
<div class="synthwave-sun"></div>
<div class="synthwave-text">RETRO</div>
<div class="synthwave-grid"></div>
</div>
</div>.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
box-sizing: border-box
}
.demo-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 280px;
overflow: hidden;
position: relative;
background: rgb(10, 10, 15);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.synthwave-scene {
width: 300px;
height: 200px;
background: linear-gradient(180deg, #0a001a 0%, #1a0033 30%, #2d004d 50%, #ff006e 51%, #ff006e 52%, #0a001a 52%);
border-radius: 12px;
position: relative;
overflow: hidden;
}
.synthwave-sun {
position: absolute;
width: 80px;
height: 80px;
background: linear-gradient(180deg, #ff9900, #ff006e, #cc00ff);
border-radius: 50%;
top: 20%;
left: 50%;
transform: translateX(-50%);
box-shadow: 0 0 40px #ff006e, 0 0 80px #ff006e66;
clip-path: polygon(0 0, 100% 0, 100% 40%, 0 40%, 0 45%, 100% 45%, 100% 55%, 0 55%, 0 60%, 100% 60%, 100% 100%, 0 100%);
}
.synthwave-grid {
position: absolute;
bottom: 0;
left: -50%;
width: 200%;
height: 48%;
background:
linear-gradient(90deg, transparent 0%, rgba(255, 0, 110, 0.3) 49.5%, #ff006e 50%, rgba(255, 0, 110, 0.3) 50.5%, transparent 100%),
repeating-linear-gradient(90deg, transparent, transparent calc(10% - 1px), rgba(255, 0, 110, 0.4) calc(10% - 1px), rgba(255, 0, 110, 0.4) 10%),
repeating-linear-gradient(0deg, transparent, transparent 20px, rgba(255, 0, 110, 0.3) 20px, rgba(255, 0, 110, 0.3) 21px);
transform: perspective(200px) rotateX(60deg);
transform-origin: center top;
animation: synthGrid 2s linear infinite;
}
.synthwave-text {
position: absolute;
top: 8%;
width: 100%;
text-align: center;
font-family: 'Inter', sans-serif;
font-weight: 900;
font-size: 20px;
letter-spacing: 4px;
text-transform: uppercase;
background: linear-gradient(180deg, #00ffff, #ff00ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
filter: drop-shadow(0 0 10px #ff00ff) drop-shadow(0 0 20px #00ffff);
}
.synthwave-stars {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 52%;
overflow: hidden;
}
.synthwave-stars span {
position: absolute;
width: 2px;
height: 2px;
background: #fff;
border-radius: 50%;
animation: twinkle 3s ease-in-out infinite;
}
.synthwave-stars span:nth-child(1) {
top: 10%;
left: 15%;
animation-delay: 0s;
}
.synthwave-stars span:nth-child(2) {
top: 20%;
left: 75%;
animation-delay: 0.7s;
}
.synthwave-stars span:nth-child(3) {
top: 5%;
left: 45%;
animation-delay: 1.2s;
}
.synthwave-stars span:nth-child(4) {
top: 30%;
left: 85%;
animation-delay: 0.3s;
}
.synthwave-stars span:nth-child(5) {
top: 8%;
left: 60%;
animation-delay: 1.8s;
}
.synthwave-stars span:nth-child(6) {
top: 25%;
left: 25%;
animation-delay: 2.1s;
}
.synthwave-stars span:nth-child(7) {
top: 15%;
left: 90%;
animation-delay: 0.9s;
}
.synthwave-stars span:nth-child(8) {
top: 35%;
left: 10%;
animation-delay: 1.5s;
}
@keyframes synthGrid {
0% {
background-position-y: 0px;
}
100% {
background-position-y: 21px;
}
}
@keyframes twinkle {
0%,
100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}/* Pure CSS — no JavaScript required */Cursed Blood Goo
Liquid blood rendered as metaballs: it drips, merges, flows and pools at the bottom. The cursor carries a glob that flings droplets and attracts nearby blood. Fully customizable color (slime, ink, lava…).
💡 Perfect for: Halloween gore hero, bleeding 404 page, horror film intro, event header, premium section background, loading screen.
View source code
<div class="demo-preview">
<div class="goo-scene" data-goo data-color="#c81e2d">
<canvas class="goo-canvas"></canvas>
<div class="goo-hint" aria-hidden="true">Sweep the blood…</div>
</div>
</div>.demo-preview,
.demo-preview *,
.demo-preview *::before,
.demo-preview *::after {
box-sizing: border-box
}
.demo-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 200px;
position: relative;
background: rgb(10, 10, 15);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.goo-scene {
position: relative;
width: 100%;
aspect-ratio: 16 / 10;
border-radius: 14px;
overflow: hidden;
isolation: isolate;
background: #0b0406;
cursor: crosshair;
}
.goo-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
filter: blur(7px) contrast(16);
}
.goo-hint {
position: absolute;
bottom: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 2;
font-size: 12px;
color: #ff5a5a;
opacity: .6;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.goo-scene {
cursor: auto;
}
}(function() {
/* ============================================================
JS scopé (IIFE) — Cursed Blood Goo (fx-0803)
Metaballs de sang sur chaque [data-goo]
============================================================ */
(function() {
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var MAX = 130;
document.querySelectorAll('[data-goo]').forEach(function(scene) {
var canvas = scene.querySelector('.goo-canvas');
var ctx = canvas.getContext('2d');
var color = scene.dataset.color || '#c81e2d';
var dpr = Math.min(devicePixelRatio || 1, 2);
var W, H, drops = [],
pool = 0,
frame = 0;
var mx = -999,
my = -999,
on = false,
bx = -999,
by = -999,
pmx = -999,
pmy = -999;
function resize() {
W = canvas.width = scene.clientWidth * dpr;
H = canvas.height = scene.clientHeight * dpr;
}
resize();
window.addEventListener('resize', resize);
scene.addEventListener('pointermove', function(e) {
var r = scene.getBoundingClientRect();
mx = (e.clientX - r.left) * dpr;
my = (e.clientY - r.top) * dpr;
on = true;
});
scene.addEventListener('pointerleave', function() {
on = false;
mx = my = -999;
});
function blob(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 6.2832);
ctx.fill();
}
function render() {
ctx.fillStyle = '#0b0406';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = color;
// flaque (surface ondulée)
var surf = H - pool;
ctx.beginPath();
ctx.moveTo(0, H + 2);
ctx.lineTo(0, surf);
for (var x = 0; x <= W; x += 18 * dpr) ctx.lineTo(x, surf + Math.sin(x * 0.012 + frame * 0.05) * 6 * dpr);
ctx.lineTo(W, H + 2);
ctx.closePath();
ctx.fill();
// gouttes
for (var i = 0; i < drops.length; i++) blob(drops[i].x, drops[i].y, drops[i].r);
// glob du curseur
if (on && bx > -500) blob(bx, by, 34 * dpr);
}
if (reduceMotion) {
// scène figée : flaque + quelques gouttes au repos
pool = H * 0.22;
for (var i = 0; i < 9; i++) drops.push({
x: (0.12 + Math.random() * 0.76) * W,
y: (0.2 + Math.random() * 0.45) * H,
r: (12 + Math.random() * 14) * dpr
});
render();
return;
}
(function loop() {
requestAnimationFrame(loop);
frame++;
// dégoulinade depuis le haut
if (frame % 11 === 0 && drops.length < MAX) drops.push({
x: Math.random() * W,
y: -12 * dpr,
vx: (Math.random() - 0.5) * 0.5,
vy: 1 + Math.random() * 1.4,
r: (9 + Math.random() * 9) * dpr
});
var floorY = H - pool;
for (var i = drops.length - 1; i >= 0; i--) {
var d = drops[i];
d.vy += 0.28 * dpr;
d.vx *= 0.99;
d.x += d.vx;
d.y += d.vy;
if (on) {
var ax = mx - d.x,
ay = my - d.y,
dist = Math.hypot(ax, ay);
if (dist < 150 * dpr && dist > 1) {
var f = (1 - dist / (150 * dpr)) * 0.7;
d.vx += ax / dist * f;
d.vy += ay / dist * f;
}
}
if (d.y + d.r * 0.4 >= floorY) {
pool = Math.min(H * 0.5, pool + (d.r * d.r) / W * 0.8);
drops.splice(i, 1);
continue;
}
if (d.x < -40 * dpr || d.x > W + 40 * dpr) drops.splice(i, 1);
}
pool = Math.max(0, pool - 0.14 * dpr); // la flaque s'écoule lentement → équilibre
if (on) {
bx += (mx - bx) * 0.3;
by += (my - by) * 0.3;
var sp = Math.hypot(mx - pmx, my - pmy);
if (sp > 13 * dpr && drops.length < MAX) drops.push({
x: bx + (Math.random() - 0.5) * 22 * dpr,
y: by + (Math.random() - 0.5) * 22 * dpr,
vx: (mx - pmx) * 0.07,
vy: (my - pmy) * 0.07 + 1,
r: (6 + Math.random() * 6) * dpr
});
}
pmx = mx;
pmy = my;
render();
})();
});
})();
})();