Home  >  Q&A  >  body text

How to make an inset shadow in SVG

I need to make a box with an inline shadow, just like CSS3 has an inline box shadow. What I've found so far is a filter with feGaussianBlur, but the problem is that it also adds a shadow outside the box, which I don't want. This is 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 made a demo and compared this code with the desired CSS production result. This filter works not only on rectangles, but also on trapezoids and more complex polygons.

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

P粉311089279P粉311089279388 days ago675

reply all(2)I'll reply

  • P粉287345251

    P粉2873452512023-10-22 11:16:24

    Based mostly on experiments I found , this is what I came 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 to see padding, uncomment the last . Unfortunately, fill="transparent" does not give the filter a usable alpha, nor does it produce a shadow.

    reply
    0
  • P粉216203545

    P粉2162035452023-10-22 11:00:31

    If you have solid padding, you can add

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

    Towards the end of the filter, it will clip the blur to the shape of the SourceGraphic. Since your shape is transparent, you need to do more work. I recommend using a semi-transparent fill on the original shape so you get the correct composition selection, and using feFuncA to zero out the fill for the final operation. This turns out to be very complicated. But here is a solution that works for any solid line 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>

    This is my fiddle branch: http://jsfiddle.net/kkPM4/

    reply
    0
  • Cancelreply