I've got an SVG that I'm working on, and I've made a border around my design and "hollowed out" the middle using a mask. Now I'd like to apply a drop shadow to the inside of the border to make it look like it's sitting just above the rest of the elements.
Here's my SVG so far:
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="640">
<defs>
<!-- The bezel -->
<mask id="bezelMask">
<!-- Make everything fully opaque -->
<rect width="100%" height="100%" fill="white" />
<!-- Then make a circle with a smaller radius -->
<circle cx="50%" cy="50%" r="43.75%" fill="black" />
<!-- Then unmask the bottom part of the bezel -->
<rect width="100%" height="6.25%" y="65%" fill="white" />
<!-- And finally, mask everything below that -->
<rect width="100%" height="35%" y="71.25%" fill="black" />
</mask>
</defs>
<g id="circular-face">
<!-- Add the bezel -->
<circle id="bezel" r="50%" height="100%" cx="50%" cy="50%" fill="red" mask="url(#bezelMask)" />
</g>
</svg>
I've tried a few different things (<feDropShadow>
, CSS styling etc.) but I can't seem to get it to do anything on the masked out portion.
I'm thinking that I need to convert my shape into a path, or at least make a path that sits underneath to make the shadow show up, but now I'm just curious if it's possible to do a drop shadow on the inside of an SVG mask
So can I make a drop shadow that affects the inside portion of a masked out shape?
As commented by Michael Mullany: you need a wrapping <g>
element to apply a filter to a masked (or clipped) element. Otherwise, the filter result get's "cut-out" as well.
To apply the inset shadow to the inner shape only you need an extra <mask>
defining only this area. Then you can apply a custom inset-shadow filter.
See also "How to create an inset shadow(transparent background) in SVG?".
body {
background: #ccc
}
.innerShadow {
fill: white;
filter: url(#insetShadow);
}
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="640">
<defs>
<!-- The bezel -->
<mask id="bezelMask">
<!-- Make everything fully opaque -->
<rect width="100%" height="100%" fill="white" />
<!-- Then make a circle with a smaller radius -->
<circle cx="50%" cy="50%" r="43.75%" fill="black" />
<!-- Then unmask the bottom part of the bezel -->
<rect width="100%" height="6.25%" y="65%" fill="white" />
<rect width="100%" height="35%" y="71.25%" fill="black" />
</mask>
<!-- mask for inner shape -->
<mask id="maskInner">
<rect width="100%" height="100%" fill="black" />
<circle cx="50%" cy="50%" r="43.75%" fill="white" />
<rect width="100%" height="100%" y="65%" fill="black" />
</mask>
<!-- based on https://stackoverflow.com/questions/53502987/how-to-create-an-inset-shadowtransparent-background-in-svg#53503687 -->
<filter id="insetShadow" width="200%" height="200%">
<!-- Shadow Offset -->
<feOffset dx="10" dy="10" />
<!-- Shadow Blur -->
<feGaussianBlur stdDeviation="10" result="offset-blur" />
<!-- Invert the drop shadow to create an inner shadow -->
<feComposite operator="out" in="SourceGraphic" result="inverse" />
<!-- Color & Opacity -->
<feFlood flood-color="black" flood-opacity=".75" result="color" />
<!-- Clip color inside shadow -->
<feComposite operator="in" in="color" in2="inverse" result="shadow" />
<!-- Shadow Opacity -->
<feComponentTransfer in="shadow" result="shadow">
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
</filter>
</defs>
<circle id="bezel" r="50%" height="100%" cx="50%" cy="50%" fill="red" mask="url(#bezelMask)" />
<!-- inset shadow area -->
<g class="innerShadow">
<circle id="inner" r="50%" height="100%" cx="50%" cy="50%" fill="white" mask="url(#maskInner)" />
</g>
</svg>
If you don't need to animate the mask you may also opt for a clip-path instead:
body {
background: #ccc
}
.innerShadow {
fill: white;
filter: url(#insetShadow);
}
<h3>Clip-path</h3>
<svg width="640" height="640">
<!-- add rectangular path in counter clockwise drawing direction -->
<path d="M0 0 v640 h640 v-640 z
M600 320c0 33.7-6 66.1-16.9 96h-526.2c-10.9-29.9-16.9-62.3-16.9-96 0-154.6 125.4-280 280-280s280 125.4 280 280z" />
</svg>
<h3>Inner shape</h3>
<svg width="640" height="640">
<path d="M600 320c0 33.7-6 66.1-16.9 96h-526.2c-10.9-29.9-16.9-62.3-16.9-96 0-154.6 125.4-280 280-280s280 125.4 280 280z" id="arc" />
</svg>
<h3>Clipped SVG</h3>
<svg width="640" height="640">
<defs>
<!-- inset shadow -->
<filter id="insetShadow" width="200%" height="200%">
<feOffset dx="10" dy="10" />
<feGaussianBlur stdDeviation="10" result="offset-blur" />
<feComposite operator="out" in="SourceGraphic" result="inverse" />
<feFlood flood-color="black" flood-opacity=".75" result="color" />
<feComposite operator="in" in="color" in2="inverse" result="shadow" />
<feComponentTransfer in="shadow" result="shadow">
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
</filter>
<!-- inner shape -->
<path d="M600 320c0 33.7-6 66.1-16.9 96h-526.2c-10.9-29.9-16.9-62.3-16.9-96 0-154.6 125.4-280 280-280s280 125.4 280 280z" id="arc" />
<!-- clip-path: negative shape-->
<clipPath id="clip">
<!-- add rectangular path in counter clockwise drawing direction -->
<path d="M0 0 v640 h640 v-640 z
M600 320c0 33.7-6 66.1-16.9 96h-526.2c-10.9-29.9-16.9-62.3-16.9-96 0-154.6 125.4-280 280-280s280 125.4 280 280z" />
</clipPath>
</defs>
<!-- border -->
<use href="#arc" clip-path="url(#clip)" stroke="red" fill="none" stroke-width="80"/>
<!--inset shadow-->
<use href="#arc" class="innerShadow"/>
</svg>
In this case we create a <path>
element for the inner shape in a graphic editor like inkscape using path-operations like subtract.
We can make this path reusable by creating a <defs>
element. The visible stroke can be drawn by an actual stroked instance of the inner shape which is cut-out by a clip-path.
While this approach may appear quite complicated we get a slightly more compact SVG and can circumvent some safari/webkit related mask rendering issues (e.g sometimes rendering these at a fixed resolution resulting in blurry renderings when zooming).