Joeytje50
Joeytje50

Reputation: 19122

How to make an inset drop shadow in SVG

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.

Upvotes: 14

Views: 31231

Answers (3)

Michael Mullany
Michael Mullany

Reputation: 31805

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

<div style="width:100px;height:100px;border:4px solid black;box-shadow:inset 0 0 30px black;margin:10px;"></div>
<svg>
    <defs>
        <filter id="inset-shadow" >
            
            <feComponentTransfer in="SourceAlpha" result="inset-selection">
                <feFuncA type="discrete" tableValues="0 1 1 1 1 1"/>
            </feComponentTransfer>
            
            <feComponentTransfer in="SourceGraphic" result="original-no-fill">
                <feFuncA type="discrete" tableValues="0 0 1"/>
            </feComponentTransfer>
 
            <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>

            <feComposite operator="in" in="inset-selection" in2="blur" result="inset-blur"/>
 
            <feComposite operator="over" in="original-no-fill" in2="inset-blur"/>            
        </filter>
    </defs>
    <rect x="10" y="10" width="100" height="100"
    stroke="black" stroke-width="4" fill="blue" fill-opacity="40%" filter="url(#inset-shadow)"/>
</svg>

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

Upvotes: 12

xialvjun
xialvjun

Reputation: 1248

Maybe we can combine ClipPath and GaussianBlur

<svg viewBox="0 0 100 100">
  <defs>    
    <path id="p" d="M10,10 l40,20 l40,-20 v80 h-80 z" />
    <filter id="f">
      <feGaussianBlur in="SourceGraphic" stdDeviation="2" />
    </filter>
  </defs>
  <clipPath id="c">
    <use href="#p" />
  </clipPath>
  <use clip-path="url(#c)" href="#p" fill="transparent" stroke="red" filter="url(#f)" />
</svg>

Upvotes: 0

Steven Vachon
Steven Vachon

Reputation: 3990

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.

Upvotes: 9

Related Questions