Introduction
Here’s the background of the story: Some time ago, a colleague came to me with this question:
Colleague A: Is there a way in CSS to replicate an element’s style without adding extra DOM elements?
For this problem, there are actually two methods. One is using the element’s background, though its browser compatibility is limited. The other, and our main focus today, is the use of feMerge and feMergeNode, two powerful SVG filter elements.
Let’s get started!
The feMerge Filter
The <feMerge>
element in SVG allows multiple filter effects to be combined, instead of applying them one after another. The final effect is achieved using <feMergeNode>
child elements, which take input from the output of other filters or built-in input keywords. One commonly used keyword is SourceGraphic, representing the original resource to which the filter is applied. When using an SVG filter in CSS, SourceGraphic refers to the HTML element targeted by the CSS rule.
The MDN documentation for feMerge
includes an example that closely matches the implementation code for the DOM style duplication requirement mentioned earlier.
Here’s the SVG code:
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<filter id="feOffset" x="-40" y="-20" width="100" height="200">
<feOffset in="SourceGraphic" dx="60" dy="60" />
<feGaussianBlur stdDeviation="5" result="blur2" />
<feMerge>
<feMergeNode in="blur2" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<rect
x="40"
y="40"
width="100"
height="100"
style="stroke: #000000; fill: green; filter: url(#feOffset);" />
</svg>
You can see that the original green rectangle element is rendered as two rectangles in the final output, with one of them offset.
Building on this concept: Example Implementation
Next, we can expand on the MDN example to create an offset, semi-transparent clone of a DOM element.
After about half an hour of work, the demo is ready. Let’s check out the result — here’s a screenshot. Ta-da!
As you can see, after applying the SVG filter to a simple button element, you now see a semi-transparent copy of the button.
The complete SVG code is as follows:
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
<filter id="offsetOpacity" width="100" height="200">
<feOffset in="SourceGraphic" dx="20" dy="20" />
<feComponentTransfer>
<feFuncA type="linear" slope="0.5" />
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</svg>
At this point, to apply a semi-transparent offset shadow to the element, all you need is a single line of CSS, like this:
button {
filter: url(#offsetOpacity);
}
How It Works
<feComponentTransfer>
is typically used together with its child elements <feFuncR>
, <feFuncB>
, <feFuncG>
, and <feFuncA>
to modify the R, G, B, or A color channels of an image.
Therefore, by using <feComponentTransfer>
and <feFuncA>
, we can achieve a semi-transparent effect on an SVG graphic.
As for the <feOffset>
element, it is used to offset the position of the graphic.
By combining these two elements, this combination allows us to duplicate a DOM element’s style with an offset and semi-transparency effect.
Combining with SMIL Animation
SVG SMIL animation can be used in filter elements, enabling us to animate the projected graphic, whether by shifting its position or changing its transparency.
The following SVG code demonstrates an effect where the opacity continuously fluctuates between 0% and 50%:
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
<filter id="offsetOpacityAnimate" width="100" height="200">
<feOffset in="SourceGraphic" dx="20" dy="20">
<!-- Chrome need this for animation -->
<animate attributeName="x" />
</feOffset>
<feComponentTransfer>
<feFuncA type="linear" slope="0.5">
<animate attributeName="slope" values="0.5;0;0.5" dur="3s" repeatCount="indefinite" />
</feFuncA>
</feComponentTransfer>
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</svg>
It’s odd, but in Chrome, you need to include a non-functional <animate>
element within the <feOffset>
filter for it to work, even though it doesn’t have any effect. Firefox doesn’t have this issue, so this seems to be a bug in Chrome.
The final result is shown in the screenshot below:
feMergeNode
<feMergeNode>
must be a child element of <feMerge>
, and its purpose is to take the result of other filters as input.
It supports the in attribute, which specifies the result of a filter as input.
If the in attribute is not specified, the default input is the output of any external filter that doesn’t have a specified result.
There’s not much more to add about this element itself.
A few thoughts
This is just a quick look at the capabilities of SVG filters.
SVG filters are very powerful, and many more filter elements will be introduced in the future.
If you can master these techniques and build up enough experience, you can definitely become an expert in graphic design — and a highly competitive one at that.
Because the technical barrier is high, but the results are impressive.
When the time comes, I’ll create an SVG filter cookbook.