Using SVG Filters in CSS

Firstly, you’ll need the svg itself.

<svg style="height:0; overflow:hidden;">
<defs>
  <filter id="wavy2">
    <feTurbulence x="0" y="0" baseFrequency="0.02" numOctaves="5" seed="1"></feTurbulence>
    <feDisplacementMap in="SourceGraphic" scale="20"></feDisplacementMap>
  </filter>
  <filter id="goo"><feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"></feGaussianBlur>    
    <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9" result="goo"></feColorMatrix>
    <feComposite in="SourceGraphic" in2="goo" operator="atop"></feComposite>
  </filter>
</defs>
</svg>

Three things to note:

1: Like empty images, empty svgs still take up some minimum space by default. If you’re borrowing a filter from an svg image that you’re actually using, that’s not an issue. If you’re just using the svg for filters, the svg itself needs to be hidden but still part of the page. You can do this by making it take up 0px rather than using display:hidden;

<svg style="height:0; overflow:hidden;">

2: If you want to use multiple filters, you can include them all in the <defs> area of a single <svg>, I find this just helps keeps my code organized.

3: Each <filter> tag needs a completely unique id. (Remember that there should never be more than one element on a page using the same id; this becomes critical any time you’re referring to that id with anything except css.)

<filter id="wavy2">

On to the CSS

You can now apply the filter in your style sheet with the same filter: attribute as the built-in filters:

.wavy2 {
    filter: url(#wavy2);
}

Two things to note here:

1: There can’t be ” around the filter id; that trips me up sometimes since image url() do take quotes around them.

2: Filters apply to the entire element including contents, and unlike transformations, can’t be undone by applying contradictory styles to a child element.

If you want to apply the filter only to the background, you will need to have the background (and therefore the filter) separated from the content and positioned behind it, so that the other child elements aren’t affected. You could use a placeholder element of some sort, but :before works just fine:

.torn-parchment, .torn-parchment > * {
    position: relative;
}
.torn-parchment::before {
    box-shadow: 2px 3px 10px var(--subtle), 0 0 125px #8f5922 inset;
    background: #fffef0 var(--parchment-texture-image);
}
.torn-parchment::before {
    content: '';
    display: block;
    position: absolute !important;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    filter: url(#wavy2);
}

The position:relative on the element is needed on the parent to constrain the absolutely positioned element, and on the child elements to bring those into the same stacking context so they don’t get hidden behind the background. If you are already using :before for something else, you could do exactly the same with :after instead, but in that case you’ll also need to add some z-indexes to bring the contents up in front of the background.

No filter vs. filtered element vs. filtered background