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:
- Separate the color channels using
feColorMatrix
- Offset each channel with
feOffset
- Blur each channel with
feGaussianBlur
- Recombine the channels using
feBlend
Here’s that in SVG filter markup:
<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.
.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:
- Row 1: Red output
- Row 2: Green output
- Row 3: Blue output
- Row 4: Alpha output
For isolating the red channel, we use:
1 0 0 0 0 All red from red channel0 0 0 0 0 No green from green channel0 0 0 0 0 No blue from blue channel0 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.
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:
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.