February 7, 2020

Hand-Drawn Underlines with SVG Filters

  • css
  • svg

In one of my early designs for this website, I put a bold underline under each top-level title. Though I liked the general look, the clean lines made it feel a little impersonal. It turned out to be quite easy to make them look handdrawn thanks to the magic of SVG filters.

An SVG filter is a combination of so-called filter primitives and lets you define all kinds of creative effects. Once defined, an SVG filter is not rendered itself. In order to use it, you can apply it to SVG or HTML elements. Browser support goes further back than you probably need.

In order to make the straight underlines in my design look slightly less perfect, I used a combination of the following filter primitives:

  • feTurbulence — generates a "cloudy" image that will serve as the base for the distortion. Altering its attributes will alter the horizontal or vertical amount of distortion.
  • feDisplacementMap — will take the turbulence image and distort the original image based on its color values.
  • feGaussianBlur — will blur the distorted image by 1 pixel to smooth the edges
  • feColorMatrix — will increase contrast and "undo" the blur (while keeping the smooth edges)
  • feOffset — will correct the lines vertical position by 2 pixels

The entire filter definition looks like this:

<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
		<filter id="handdrawn" x="-20%" y="-20%" width="140%" height="140%">
			<feTurbulence type="fractalNoise" basefrequency="0.001 0.01" numoctaves="26" stitchtiles="stitch" result="turbulence"/>
			<feDisplacementMap in="SourceGraphic" in2="turbulence" scale="20" xchannelselector="R" ychannelselector="G" result="displacementMap"/>
			<feGaussianBlur in="displacementMap" stddeviation="1" color-interpolation-filters="sRGB" result="blur"/>
			<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7" result="contrast"/>
			<feOffset in="contrast" dy="2"/>

The only step left here is to apply the filter through CSS. Because I only wanted the underline to be affected, I put two identical elements on top of each other:

  • The first one contains the text.
  • The second one contains the underline (a CSS linear-gradient background).

You can see the combined result in the following CodePen: