htmlcsssvgmask

How do I use this SVG filter as an animated CSS mask?


I want to put a horizontal line with a subtle moving-fog opacity effect beneath the headings on a webpage.

I've figured out a way to get all but the last of my desired points using a mix of CSS and SVG masks and filters:

html {
    background: black;
    font-size: 16px;
    margin: 0;
}
.first-heading {
    margin: 0.9375rem 3.5rem;
    font-family: serif;
    font-weight: 400;
    font-style: normal;
    color: #bdbdbd;
}
.page-heading {
    width: 100%;
    display: inline-flex;
    position: relative;
}
.page-heading::after {
    content: "";
    position: absolute;
    left: 0;
    width: 100%;
    height: 100%;
    border-bottom: 0.125rem solid gold;
    filter: drop-shadow(0 0 0.5rem gold) drop-shadow(0 0 0.5rem gold) url(#filter_gold);
    mask: linear-gradient(0.25turn, black 0%, white 6.25%, white 93.75%, black 100%) luminance no-clip, url(#mask_fog) luminance no-clip repeat;
}
<svg id="svg-defs" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" height="0" style="display: block">
    <defs>
        <filter id="filter_gold" color-interpolation-filters="sRGB" x="0" y="0" width="512px" height="512px">
            <feTurbulence
                type="fractalNoise"
                stitchTiles="stitch"
                baseFrequency="0.0078125"
                numOctaves="1"
                result="turbulence"
            />
            <feColorMatrix
                in="turbulence"
                type="matrix"
                values="0.2126 0.7152 0.0722 0 0
                    0.2126 0.7152 0.0722 0 0
                    0.2126 0.7152 0.0722 0 0
                    0.0000 0.0000 0.0000 0 1"
                result="grayscale"
            />
            <feComponentTransfer in="grayscale" result="rescaled">
                <feFuncR type="linear" slope="3" intercept="-1"/>
                <feFuncG type="linear" slope="3" intercept="-1"/>
                <feFuncB type="linear" slope="3" intercept="-1"/>
            </feComponentTransfer>
            <feComponentTransfer color-interpolation-filters="sRGB" in="rescaled" result="duotone">
                <feFuncR type="table" tableValues="0.75 0.96875"/>
                <feFuncG type="table" tableValues="0.611328125 0.84375"/>
                <feFuncB type="table" tableValues="0.22265625 0.486328125"/>
                <feFuncA type="table" tableValues="0 1"/>
            </feComponentTransfer>
            <feComposite
                in="duotone"
                in2="SourceGraphic"
                operator="in"
                result="duotone-clipped"
            />
        </filter>
        <filter id="filter_fog" color-interpolation-filters="sRGB" x="0" y="0" width="512px" height="512px">
            <feTurbulence
                type="fractalNoise"
                stitchTiles="stitch"
                baseFrequency="0.00390625"
                numOctaves="1"
                result="turbulence"
            />
            <feColorMatrix
                in="turbulence"
                type="matrix"
                values="0.2126 0.7152 0.0722 0 0
                    0.2126 0.7152 0.0722 0 0
                    0.2126 0.7152 0.0722 0 0
                    0.0000 0.0000 0.0000 0 1"
                result="grayscale"
            />
            <feComponentTransfer in="grayscale" result="rescaled">
                <feFuncR type="linear" slope="2.25" intercept="-0.5"/>
                <feFuncG type="linear" slope="2.25" intercept="-0.5"/>
                <feFuncB type="linear" slope="2.25" intercept="-0.5"/>
            </feComponentTransfer>
        </filter>
    </defs>
    <rect id="mask_fog" x="0" y="0" width="512" height="512" filter="url(#filter_fog)"/>
</svg>
<div class="page-heading">
    <h1 class="first-heading">Heading</h1>
</div>

However, I can't figure out how to make the mask_fog work to modify the line's transparency: if I make it the second mask it appears to have no effect, and if I make it the first mask the line completely disappears. I also tried using just the filter_fog <filter> as the mask, with the same results.

Below is a mock-up of what I want to achieve, made with After Effects (using screenshots of the SVG filter effects from the above code): Animated GIF of a line with the effects described above.


Solution

  • you can’t directly “animate” an SVG <filter> used as a CSS mask browsers don’t re-render filters unless something inside changes. So your <feTurbulence> is static by default.

    The simple fix is to animate the baseFrequency in SVG, not in CSS. Like this:

    <feTurbulence id="fogNoise" type="fractalNoise" baseFrequency="0.004" numOctaves="1" stitchTiles="stitch">
      <animate attributeName="baseFrequency" dur="8s" values="0.004;0.008;0.004" repeatCount="indefinite"/>
    </feTurbulence>
    

    Then reference that filter as your mask:

    .page-heading::after {
      mask: url(#fogNoise);
      -webkit-mask: url(#fogNoise);
    }
    

    That’s it — the <animate> tag makes the noise “breathe” over time, giving you that subtle fog shimmer. No JS needed. Have a good day!