How to make an inset drop shadow in SVG

23,359

Solution 1

If you had a solid fill, you could just add

<feComposite operator="in" in2="SourceGraphic"/> 

to the end of your filter and it would clip the blur to the shape of the SourceGraphic. Since your shape is transparent, you'll have to do a little more work. What I'd suggest is using a semi-transparent fill on the original graphic in order to get the right selection for compositing and using an feFuncA to zero out the fill for the final operation. This turns out to be surprisingly complicated. But here is a solution that will work for any solid-stroke shape

<filter id="inset-shadow" >
            <!-- dial up the opacity on the shape fill to "1" to select the full shape-->
            <feComponentTransfer in="SourceAlpha" result="inset-selection">
                <feFuncA type="discrete" tableValues="0 1 1 1 1 1"/>
            </feComponentTransfer>

            <!-- dial down the opacity on the shape fill to "0" to get rid of it -->
            <feComponentTransfer in="SourceGraphic" result="original-no-fill">
                <feFuncA type="discrete" tableValues="0 0 1"/>
            </feComponentTransfer>

            <!-- since you can't use the built in SourceAlpha generate your own -->
            <feColorMatrix type="matrix" in="original-no-fill" result="new-source-alpha" values="0 0 0 0 0
                      0 0 0 0 0
                      0 0 0 0 0
                      0 0 0 1 0"
/>            

            <feGaussianBlur in="new-source-alpha" result="blur" stdDeviation="5" />
            <feGaussianBlur in="new-source-alpha" result="blur2" stdDeviation="10" />
            <feGaussianBlur in="new-source-alpha" result="blur3" stdDeviation="15" />
            <feMerge result="blur">
                <feMergeNode in="blur" mode="normal"/>
                <feMergeNode in="blur2" mode="normal"/>
                <feMergeNode in="blur3" mode="normal"/>
            </feMerge>
            <!-- select the portion of the blur that overlaps with your shape -->
            <feComposite operator="in" in="inset-selection" in2="blur" result="inset-blur"/>
             <!-- composite the blur on top of the original with the fill removed -->
            <feComposite operator="over" in="original-no-fill" in2="inset-blur"/>            
        </filter>

here is my fork of your fiddle: http://jsfiddle.net/kkPM4/

Solution 2

largely based on an experiment that I found, here's what I've come up with:

<defs><filter id="inset-shadow">
    <feOffset dx="10" dy="10"/>                                                         <!-- Shadow Offset -->
    <feGaussianBlur stdDeviation="10"  result="offset-blur"/>                           <!-- Shadow Blur -->
    <feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/> <!-- Invert the drop shadow to create an inner shadow -->
    <feFlood flood-color="black" flood-opacity="1" result="color"/>                     <!-- Color & Opacity -->
    <feComposite operator="in" in="color" in2="inverse" result="shadow"/>               <!-- Clip color inside shadow -->
    <feComponentTransfer in="shadow" result="shadow">                                   <!-- Shadow Opacity -->
        <feFuncA type="linear" slope=".75"/>
    </feComponentTransfer>
    <!--<feComposite operator="over" in="shadow" in2="SourceGraphic"/>-->                       <!-- Put shadow over original object -->
</filter></defs>

<rect width="100" height="100" fill="yellow" filter="url(#inset-shadow)"/>

If you want the fill to be seen, uncomment the last <feComposite>. Unfortunately, fill="transparent" will not give the filter an alpha to work with and will produce no shadow.

Share:
23,359
Joeytje50
Author by

Joeytje50

I am a Master student in Computer Science and hobbyist web developer, experienced in html css3 js regex and a beginner in svg php python c++. I like to think outside the box to find alternative uses for existing features, such as with my CSS3 mineturtle, or with this supposedly impossible question. I came up with CSS3 :checked-based tab menus independently in 2011 (back when IE8 was still by far the most used browser). A personal dislike of mine is jQuery UI's default styling being used so often without being customised. The visited item on your own userprofile that shows the dates you logged in, for example, just doesn't blend in with the other styles at all. &lt;/rant&gt;

Updated on July 09, 2022

Comments

  • Joeytje50
    Joeytje50 almost 2 years

    I need to make a box with an inset drop shadow, in the same way that CSS3 has inset box-shadows. What I've found so far is a filter with feGaussianBlur, but the problem with that is that it also adds a drop shadow outside the box, which I don't want. Here's the code I've got so far:

    <svg>
        <defs>
            <filter id="drop-shadow">
                <feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5" />
                <feGaussianBlur in="SourceAlpha" result="blur2" stdDeviation="10" />
                <feGaussianBlur in="SourceAlpha" result="blur3" stdDeviation="15" />
                <feMerge>
                    <feMergeNode in="blur" mode="normal"/>
                    <feMergeNode in="blur2" mode="normal"/>
                    <feMergeNode in="blur3" mode="normal"/>
                    <feMergeNode in="SourceGraphic" mode="normal"/>
                </feMerge>
            </filter>
        </defs>
        <rect x="10" y="10" width="100" height="100"
        stroke="black" stroke-width="4" fill="transparent" filter="url(#drop-shadow)"/>
    </svg>
    

    I've made a demo that also compares this code with the desired CSS-made result. The filter should not just work on rectangles, but also on trapezoids and more complicated polygons.

    I've already tried using radialGradient, but since that makes the gradient circular, that's not good either.

  • Joeytje50
    Joeytje50 over 10 years
    This works perfectly, thanks very much! I've got one question about your fiddle though, when I tried changing the fill-opacity of the <rect>, the inset shadow suddenly disappeared when the fill-opacity got below 17% or above 66% (it is interesting to be able to get the opacity as low as possible for the browsers that don't support filters, of course), while not changing at all when changing within that range. Is there any particular reason for that?
  • Michael Mullany
    Michael Mullany over 10 years
    That's the effect of the two feFuncA's I have in there. They chop up the alpha range into seven and three ranges respectively and set any opacity in those ranges to 0, 1, 1, 1, 1, 1, 1 and 0, 0, 1 respectively. If you want to set the fill opacity very low (like .03) you'll have to adjust those tableValues appropriately (0 followed by 49 1's for example). IE only supports 64 values in that array, so you can't do an opacity lower than 1/64th.