Chromatic Aberration with SVG Filters

Published

So first off, what is chromatic aberration?

Chromatic aberration is the visual distortion that happens when a lens doesn’t focus all colors to the same point. Bright objects tend to bleed out around their edges with a distinct separation between the colors. Normally, this is considered a bad thing that professional photographers avoid with expensive camera lenses. It does look cool though, and in recent video games is commonly recreated as a stylistic effect.

TRY IT OUT ⚙️⬇️

For chromatic aberration on web, we can separate the red, green, and blue color channels of an element, offset them slightly, and add a bit of blur to taste. This emulates how different wavelengths of light bend at different angles when passing through a camera lens.

How it works

The SVG filter technique has 4 steps:

  1. Separate the color channels using feColorMatrix
  2. Offset each channel with feOffset
  3. Blur each channel with feGaussianBlur
  4. Recombine the channels using feBlend

Here’s that in SVG filter markup:

SVG
<svg>
<defs>
<filter id="chromatic-aberration">
<!-- Red channel shift right 2px and blurred 2px -->
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
/>
<feOffset dx="2" dy="0" />
<feGaussianBlur stdDeviation="2" result="redChannel" />
<!-- Green channel left in place with a small 0.5px blur -->
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0"
/>
<feGaussianBlur stdDeviation="0.5" result="greenChannel" />
<!-- Red channel shift left 2px and blurred 2px -->
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0"
/>
<feOffset dx="2" dy="0" />
<feGaussianBlur stdDeviation="2" result="blueChannel" />
<!-- Put the channels back together with the screen blend mode -->
<feBlend
in="redChannel"
in2="greenChannel"
mode="screen"
result="redGreen"
/>
<feBlend in="redGreen" in2="blueChannel" mode="screen" />
</filter>
</defs>
</svg>

Put the markup — not the .svg file — anywhere in your HTML. And then in CSS, you can reference the SVG filter definition by its ID attribute.

CSS
.chromatic-aberration {
filter: url('#chromatic-aberration');
}

Understanding the color matrix

The feColorMatrix filter effect uses a 5x4 matrix to transform RGBA values. Each row represents the output for one color channel:

For isolating the red channel, we use:

Plain Text
1 0 0 0 0 All red from red channel
0 0 0 0 0 No green from green channel
0 0 0 0 0 No blue from blue channel
0 0 0 1 0 All opacity from alpha channel

Then again for green and blue on different rows. The 5th column in each row is the offset of the channel if you want to make it more or less intense by a constant amount instead of proportionally.

Extra lo-fi with guassian blur

To really sell the lo-fi look we’re going for, feGaussianBlur adds a small amount of blur to each color channel. In my testing, I could get only half-number steps to make to be recognized across browsers. Somewhere between 0.5 and 2 for each channel has been the sweet spot for the demos in this post.

One suggestion: if applying to text, leave your brightest color channel unblurred to keep things readable.

SVG examples

You can use the filter on any element on the page as long as the SVG filter definition markup is present in your HTML. Here’s the same filter on some different color shapes in an <svg> tag.

Shapes

Notice how different colors affect the amount of visible offset.

Terminal effect

Just for funsies, I threw together a Fallout-style terminal to put the effect on a non-black background. Some vertical offset as well as horizontal time time.

ROBCO INDUSTRIES (TM) TERMLINK PROTOCOL

!!! WARNING: LOCKOUT IMMINENT !!!

1 ATTEMPT(S) LEFT: ■

0xF964 <{=<(>$$,'}' 0xFA30 ?}/+TERMS('<

0xF970 $:=:",,!SPIN 0xFA3C '**):>]<*.#T

0xF994 IRES':=,/:./ 0xFA60 [..HQ.<{[.:/H

0xFA0C *#*<.$$TRICK 0xFA78 :(||).@@=={.% >PRICE

0xFA08 :*.**%$!@#[$ 0xFA84 ).FRIES:**',# >Entry denied

0xFA04 <{!:?{**_/Q. 0xFA90 ?+[))<[,,.PR1 >0/5 correct.

0xFA00 +[?]}*<{'}<{ 0xFA9C CE?+//Q$-[:$% >TEXAS

0xFA0C *GQ:>TRIED%: 0xFAA8 {,*>[[{:.**=) >Entry denied

0xFA08 ]#'%<")-@#%* 0xFAB4 #+**:'@<@>TRI >2/5 correct.

0xFA04 %()>=:#*%>]{ 0xFAC0 ES}[._{/>TRII >TIRES

0xFA08 +?.**>'%]-*- 0xFACC E[|[TANKS:#*" >Entry denied

0xFA0C %${*`/:>/{@} 0xFAD8 <THICK:{]TRIB >2/5 correct

0xFA18 SKIES|%)>,^* 0xFAE4 E**}}.<'{.*/]

0xFA24 :%=>]%**{%[. 0xFAF0 EXAS/**:[{=-[ >TERMS

React

Want all that wrapped up in a React component? Here you go:

TSX
interface ChromaticAberrationFilterProps {
blueBlur?: number;
blueX?: number;
blueY?: number;
greenBlur?: number;
greenX?: number;
greenY?: number;
id: string;
redBlur?: number;
redX?: number;
redY?: number;
}
export const ChromaticAberrationFilter: React.FC<
ChromaticAberrationFilterProps
> = ({
blueBlur = 0,
blueX = 0,
blueY = 0,
greenBlur = 0,
greenX = 0,
greenY = 0,
id,
redBlur = 0,
redX = 0,
redY = 0,
}) => (
<svg
aria-hidden="true"
className="sr-only"
height="1"
viewBox="0 0 1 1"
width="1"
>
<defs>
<filter id={id}>
<feColorMatrix
in="SourceGraphic"
type="matrix"
values={`1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0`}
/>
<feOffset dx={redX} dy={redY} />
<feGaussianBlur stdDeviation={redBlur} result="redChannel" />
<feColorMatrix
in="SourceGraphic"
type="matrix"
values={`0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0`}
/>
<feOffset dx={greenX} dy={greenY} />
<feGaussianBlur stdDeviation={greenBlur} result="greenChannel" />
<feColorMatrix
in="SourceGraphic"
type="matrix"
values={`0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0`}
/>
<feOffset dx={blueX} dy={blueY} />
<feGaussianBlur stdDeviation={blueBlur} result="blueChannel" />
<feBlend
in="redChannel"
in2="greenChannel"
mode="screen"
result="redGreen"
/>
<feBlend in="redGreen" in2="blueChannel" mode="screen" />
</filter>
</defs>
</svg>
);

Wrap up

So that’s everything I have to share. SVG filters are extraordinarily powerful and currently underused in front-end development. It would be cool to see them get more use, especially now that GenAI tooling (Thanks, Claude!) makes the unfamiliar markup so much easier to both write and understand.

What do you think? Something you want to learn more about too? Let me know on LinkedIn or Twitter.