Using SVG Filters can be really powerful, but also pretty complicated. In particular, SVG can do drop shadows better than CSS's
box-shadow property, because it can respond to the transparency of whatever you're doing, rather than only shadowing the element's box.
However, the drop-shadow filter is not a primitive: it's built up out of the simpler filter effects, and it's fairly complex:
<filter id="drop-shadow"> <feGaussianBlur in="[alpha-channel-of-input]" stdDeviation="[radius]"/> <feOffset dx="[offset-x]" dy="[offset-y]" result="offsetblur"/> <feFlood flood-color="[color]"/> <feComposite in2="offsetblur" operator="in"/> <feMerge> <feMergeNode/> <feMergeNode in="[input-image]"/> </feMerge> </filter>
This only does a plain drop-shadow, where the element is casting a shadow below it. Modifying it to do an inset shadow, where the element is "cut out" and the light source is casting a shadow into it, isn't hard, but you have have to know what all the little bits of the original shadow are doing.
Let's get the important bit out of the way. Here's a working inset drop-shadow with SVG filters:
And here's the code for it:
<!DOCTYPE html> <svg style="border: thin solid;"> <filter id="inset-shadow" x="-50%" y="-50%" width="200%" height="200%"> <feComponentTransfer in=SourceAlpha> <feFuncA type="table" tableValues="1 0" /> </feComponentTransfer> <feGaussianBlur stdDeviation="3"/> <feOffset dx="5" dy="5" result="offsetblur"/> <feFlood flood-color="rgb(20, 0, 0)" result="color"/> <feComposite in2="offsetblur" operator="in"/> <feComposite in2="SourceAlpha" operator="in" /> <feMerge> <feMergeNode in="SourceGraphic" /> <feMergeNode /> </feMerge> </filter> <circle cx=50 cy=50 r=20 fill=red filter="url(#inset-shadow)" /> </svg>
Let's walk through this, node by node.
First, set up the filter region with the
heightattributes. By default, the entire filtering operation takes place in a box 10% larger in every direction than the source graphic. Since we're working with blurs, which can extend a good bit away from the source, we need a bit more spaaaaaace. 50% larger in all directions is generally fine.
<feComponentTransfer>We start by taking the alpha channel of the source graphic with
in="SourceAlpha", and inverting it. The plain drop-shadow uses the graphic's normal alpha channel, blurring and offsetting it to produce the shadow. Here, the source graphic itself is not casting the shadow - everything else is - so we need to invert its alpha. This gives us an image that is transparent where the image is solid, and solid where the image is transparent, which we'll turn into the shadow.
<feGaussianBlur>Now we blur that image, preparing it for shadow-ness, and then use
<feOffset>to shift it a bit, so the light source will appear to come from the side a bit.
This is also the first time we see the
resultattribute. By default, each sibling filter effect takes the output of the previous filter effect as its input. If you need do something complicated, though, you can name the output of a particular filter effect, and refer to it explicitly later.
The next two elements are somewhat non-obvious. When we grabbed the "SourceAlpha", it gave us a solid-black image with the same alpha channel as the source image. If we want a black shadow, that works fine, but if we want to color it, we need to do some work. In this case, we use
<feFlood>to generate an infinite field of color (my example just uses black again, whatever), and then, the important bit, uses
<feComposite operator="in">to chop it down to just the bits that overlap the blurred shadow.
There are a bunch of composite operations, but "in", in particular, basically multiplies the alpha channel of the first image (in this case, implicitly the result of the previous
<feFlood>effect) with the alpha channel of the second image (explicitly pointing to the
offsetblurresult). It's like using a stencil, where the solid parts of the
in2image let the
inimage show through, and the transparent parts block it.
This gives us a blur of the right color. If you just want a black shadow, you can drop this
<feFlood>and the first
<feComposite>, as the blur you generated previously starts out black.
We then do another
<feComposite operator="in">, this time using the SourceAlpha again as the stencil mask. This cuts out most of the blur, leaving us with only the parts that will overlap the source image.
Finally, we merge the shadow and the source image with
<feMerge>and the predefined
SourceImagevalue, putting the shadow second so it goes on top.
And we're done! When you reference this filter, it produces an inset drop shadow over your image.