Learn how to create distortion effects with CSS skew transforms, scale warping, and SVG displacement filters, then see it as a fullscreen scroll transition.
Hover over the preview to trigger the distortion. Adjust the controls to tweak the effect, then copy the generated CSS.
CSS Skew Text Distortion
Warp text with CSS skew transforms and scale for a dynamic distortion look
The term "distortion effect" covers several distinct techniques. Each suits different use cases:
Duplicates text layers using ::before and ::after pseudo-elements, then offsets and clips them with clip-path or clip. Animating the offsets with @keyframes at irregular intervals produces a digital glitch look. Pure CSS, no JavaScript.
Warps an image on :hover using CSS skew() and scale(), or more advanced WebGL shaders that displace pixels based on mouse position. The CSS approach is quick to implement; the WebGL approach (using libraries like curtains.js or OGL) gives per-pixel control.
Uses an inline SVG <filter> with feTurbulence and feDisplacementMap to spatially shift pixels in any HTML element. Produces organic, noise-based warps that CSS transforms cannot achieve on their own.
Combines SVG displacement maps or WebGL fragment shaders to simulate refraction through water or glass. Often used as scroll-triggered transitions between sections or as hover effects on hero images.
Ties transform values to scroll position using scroll-timeline, IntersectionObserver, or a scroll library. Text skews, stretches, or warps as the user scrolls past it.
The techniques above work for element-level interactions. For fullscreen distortion transitions between sections, with per-pixel displacement mapping on the GPU at 60 fps, you need WebGL.
Scroll inside the preview below to see the WebGL displacement transition in action:
fullPage.js adds
Scroll inside the preview to see the distortion transition between sections
| Approach | Best for | Limitations |
|---|---|---|
| CSS-only | Lightweight glitch and skew effects, text distortion, hover transitions | Limited to geometric transforms — no per-pixel displacement |
| SVG filters | True displacement/warp effects on any HTML element using noise maps | Animating filter attributes requires JavaScript; can be expensive on large elements |
| WebGL | Rich liquid/hover transitions, per-pixel displacement at 60 fps, fullscreen scroll effects | Requires a JS library or custom shaders; no fallback without <canvas> |
Start with CSS transforms for simple warps. Move to SVG filters when you need noise-based displacement. Use WebGL when you need real-time per-pixel control or fullscreen transitions.
transform can rotate, resize, distort, or move elements. For distortion specifically, skew(x, y) shears an element along one or both axes, and it's the simplest native distortion. Combining it with scale() using different X and Y values creates convincing warp effects:
.warp:hover {
transform: skew(-5deg, 2deg) scale(1.05, 0.96);
transition: transform 0.4s cubic-bezier(.25,.46,.45,.94);
}
filter processes an element before display. blur(), contrast(), and url() (pointing to an SVG filter) can all contribute to a distortion look. The url(#filter-id) syntax bridges CSS and SVG, letting you apply complex displacement filters to any HTML element:
/* Apply an SVG displacement filter via CSS */
.distort {
filter: url(#turbulence-warp);
}
feDisplacementMap spatially displaces pixels using another image or generated noise as a map. Paired with feTurbulence (which generates Perlin noise), it produces organic warps. The scale attribute controls intensity; baseFrequency controls the coarseness of the noise pattern:
<feTurbulence baseFrequency="0.03" numOctaves="3" result="noise"/>
<feDisplacementMap in="SourceGraphic" in2="noise"
scale="20" xChannelSelector="R" yChannelSelector="G"/>
Glitch distortion duplicates content into ::before and ::after layers, offsets them with translate or clip-path, and animates each layer independently with @keyframes. The stacked, out-of-sync layers create the characteristic digital glitch look.
Text distortion is one of the most searched variations of this effect. The main approaches:
Apply transform: skew() on hover or scroll to shear headline text. Combine with transition and cubic-bezier easing for smooth warps:
h1 {
transition: transform 0.5s cubic-bezier(.25,.46,.45,.94);
}
h1:hover {
transform: skewX(-8deg);
}
This is what the generator above produces.
Create two copies of the text using ::before and ::after with content: attr(data-text). Position them absolutely, offset with translate, and animate with staggered @keyframes. Add clip-path: inset() keyframes to randomly slice sections for a stronger glitch:
.glitch {
position: relative;
}
.glitch::before,
.glitch::after {
content: attr(data-text);
position: absolute;
top: 0; left: 0;
}
.glitch::before {
animation: glitch-1 0.3s infinite linear alternate;
clip-path: inset(20% 0 40% 0);
color: cyan;
}
.glitch::after {
animation: glitch-2 0.3s infinite linear alternate;
clip-path: inset(60% 0 10% 0);
color: magenta;
}
@keyframes glitch-1 {
0% { transform: translate(-2px, -1px); }
100% { transform: translate(2px, 1px); }
}
@keyframes glitch-2 {
0% { transform: translate(2px, 1px); }
100% { transform: translate(-2px, -1px); }
}
Apply an SVG feDisplacementMap filter to a heading element for organic, noise-based text warping:
h1 {
filter: url(#distort);
transition: filter 0.3s;
}
h1:hover {
filter: url(#distort-strong);
}
Use the SVG Filter demo above to dial in the turbulence frequency and displacement scale, then copy the code.
Image hover distortion is a major use case. Portfolio grids, galleries, and hero sections all benefit from it.
Apply transform: skew() and scale() to an image on :hover. Wrap in an overflow: hidden container to keep the warped image within bounds:
.img-distort {
overflow: hidden;
}
.img-distort img {
transition: transform 0.5s cubic-bezier(.25,.46,.45,.94);
}
.img-distort:hover img {
transform: skew(-3deg, 1deg) scale(1.08, 0.95);
filter: blur(0.5px);
}
For per-pixel displacement on hover (the liquid ripple effect common in agency portfolios), you need a WebGL library. The WebGL demo in the generator above uses curtains.js with a custom fragment shader that displaces UV coordinates based on mouse distance. Other options include OGL, Three.js, or PixiJS.
SVG displacement is the middle ground between CSS transforms and full WebGL. It gives real pixel-level distortion without needing a canvas or shader code.
The filter takes two inputs: the source graphic and a displacement map. For each pixel, it reads the R and G channels of the map and uses those values to shift the source pixel's position. feTurbulence generates the map procedurally, so no external image is needed:
<svg style="position:absolute;width:0;height:0">
<filter id="distort">
<feTurbulence type="turbulence"
baseFrequency="0.03"
numOctaves="3"
result="noise"/>
<feDisplacementMap
in="SourceGraphic"
in2="noise"
scale="20"
xChannelSelector="R"
yChannelSelector="G"/>
</filter>
</svg>
<!-- Apply to any element -->
<h1 style="filter: url(#distort)">Warped</h1>
CSS cannot animate feTurbulence attributes directly. Use JavaScript to update baseFrequency or scale on hover or scroll:
element.addEventListener('mousemove', function(e) {
var turbulence = document.getElementById('turb');
var val = 0.01 + (e.clientX / window.innerWidth) * 0.04;
turbulence.setAttribute('baseFrequency', val);
});
Alternatively, use SMIL <animate> tags inside the filter for a no-JS option (supported in Chrome, Firefox, and Safari).
feTurbulence and feDisplacementMap are supported in all modern browsers (97%+ global coverage), including Chrome, Firefox, Safari, and Edge. The filter: url(#id) syntax for applying SVG filters to HTML elements is universally supported.
Liquid and glass effects simulate refraction, where pixels shift as if viewed through water or frosted glass. These are popular for scroll transitions, hero backgrounds, and image reveal animations.
Use feTurbulence with low baseFrequency (0.01–0.03) and high scale values for broad, fluid warps. Animate the seed attribute to make the noise pattern shift over time:
<feTurbulence baseFrequency="0.015" seed="0" result="noise">
<animate attributeName="seed" from="0" to="100"
dur="3s" repeatCount="indefinite"/>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="40"/>
The changing seed shifts the noise pattern each frame, creating a flowing liquid effect.
For fullscreen liquid transitions between pages or sections, WebGL shaders use a displacement texture (a grayscale image) to control how pixels transition from one image to another. The displacement map defines the shape of the warp, whether circular, diagonal, noisy, etc. fullPage.js with the Cinematic extension uses this technique for scroll-triggered section transitions.
Combine backdrop-filter: blur() with an SVG displacement filter on a pseudo-element overlay:
.glass {
position: relative;
}
.glass::before {
content: '';
position: absolute;
inset: 0;
backdrop-filter: blur(12px);
filter: url(#distort);
}
The blur handles the frost; the displacement adds the warped refraction. This is CSS + SVG only, no JavaScript needed.
Distortion effects, especially glitch animations with rapid flashing, can be problematic for users with vestibular disorders or photosensitive conditions. Use prefers-reduced-motion to disable or tone down the effect:
@media (prefers-reduced-motion: reduce) {
.glitch::before,
.glitch::after {
animation: none;
}
.distort:hover {
transform: none;
filter: none;
}
}
This respects the user's OS-level motion preference. The effect still works for users who haven't opted out, while avoiding triggering symptoms for those who have.
skew(), scale(), rotate3d(), and pseudo-element glitch techniques are pure CSS. For noise-based organic warps you need SVG filters (still no JavaScript, but technically SVG, not CSS). For per-pixel displacement on images, you need WebGL.
feDisplacementMap with feTurbulence produces organic, noise-driven distortion. Use it for text or UI elements where WebGL would be overkill.
clip-path animations are the most problematic. Always wrap distortion animations in a @media (prefers-reduced-motion: reduce) query that disables or reduces the motion. Subtle hover warps without flashing are generally fine.
feDisplacementMap give the best results with minimal overhead. For hero images, WebGL provides the smoothest per-pixel displacement on hover or scroll. CSS skew() works for quick, lightweight image warps where you don't need pixel-level control.
Add scroll transitions, parallax and animations to your site with fullPage.js. One component, 80+ effects.
Powering fullscreen design for Google, Sony, BBC, eBay & more