Reputation: 826
I would like to simulate and "inside stroke" on an svg path. I have an svg map with multiple complex paths (countries) each with a different fill color stroke. And i would like to add a "fake inside stroke" in the first one. I managed to get a few things done with the inner-shadow trick (with gaussian blur filter) but can't manage to have it as "non-blurry".
The ideal solution would be as an svg filter so i can apply it dynamicaly via JS without changing path or manipulating dom.
Thanks a lot ! Edit 1 : So far i tried this trick but the fake shadow is sometimes over the stroke and always blurry so i'm not sure that's even the best way ...
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150" height="150" style="transform:scale(2);transform-origin:0 0 ">
<defs>
<filter id='inset' x='-50%' y='-50%' width='200%' height='200%'>
<feFlood fill-color="black"/>
<feComposite in2="SourceAlpha" operator="out"/>
<feGaussianBlur stdDeviation='10' edgeMode="none" />
<feOffset dx='0' dy='0' result='offsetblur'/>
<feFlood flood-color='#00ff00' result='color'/>
<feComposite in2='offsetblur' operator='in'/>
<feComposite in2='SourceAlpha' operator='in' />
<feMerge>
<feMergeNode in='SourceGraphic'/>
<feMergeNode/>
</feMerge>
</filter>
</defs>
<path class="st0" d="M144.7,126.2l-2.8,8.8l-3.9-2.3l-2-7.7l1.7-4.3l5.5-4.4L144.7,126.2z M93.5,24.2l6,6.3l4.4-1l7.5,6l1.9,1.1
l2.5-0.3l4,3.4l12.3,2.4l-4.3,8.9l-1.1,9.1l-2.4,2.2l-3.9-1.2l0.3,3.2l-6.3,7l-0.1,5.6l4.1-1.9l2.9,5.4L121,84l2.5,4.6l-3,3.7
l2.2,9.3l4.6,1.5l-1,5.1l-7.8,6.6l-16.9-3.2l-12.5,3.8l-1,7l-9.9,1.5l-9.6-5.3l-3.1,2.5l-15.8-5.3l-3.4-4.6l4.4-7.1l1.6-24.1
l-8.8-13l-6.3-6.4l-13.1-4.9l-0.9-9.4l11.1-2.8L48.9,47l-2.7-14.8l8.1,5.7l20-10.3l2.6-11l7.5-2.8l1.3,4.8l4,0.2L93.5,24.2z" stroke-width="1" fill="#00ffff" stroke="#FF0000" filter="url(#inset)"/>
</svg>
Upvotes: 8
Views: 9041
Reputation: 11313
Iterating from Don's idea here you might need to further separate each line and even add a fill with no overlaps -- perhaps because you need some transparency or texture.
If moving the shape into defs
is possible in given use-case and interactivity is not strictly necessary, it is possible to further derive disjunct masks from it that each cover distinct area: "outer border" (beach), "inner border" (cliffs), and even "fill" (inland):
:root {
background: dimgray;
color: snow;
}
svg {
--a: darkslategray;
--b: teal;
background-image: repeating-conic-gradient(var(--a) 0 25%, var(--b) 0 50%);
background-size: 1em 1em;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
width="600" height="300" viewbox="0 0 300 150">
<use
href="#canvas"
mask="url(#beach)"
fill="darkblue"
opacity=".6"
/>
<use
href="#canvas"
mask="url(#cliffs)"
fill="red"
opacity=".4"
/>
<use
href="#canvas"
mask="url(#inland)"
fill="lime"
opacity=".3"
/>
<defs>
<!--
Both "stroke halves" in a single stroke-width:
-->
<g id="coast">
<use
href="#shoreline"
stroke-width="10"
/>
</g>
<!--
Area with the outer half of the stroke:
-->
<mask id="beach">
<use
href="#coast"
stroke="white"
/>
<use
href="#shoreline"
fill="black"
/>
</mask>
<!--
For cutting the outer half of the stroke:
-->
<clipPath id="sea">
<use
href="#shoreline"
/>
</clipPath>
<!--
Area with the inner half of the stroke:
-->
<mask id="cliffs">
<use
href="#coast"
stroke="white"
clip-path="url(#sea)"
/>
</mask>
<!--
Area inside inner stroke:
-->
<mask id="inland">
<use
href="#coast"
stroke="black"
fill="white"
/>
</mask>
<!--
Viewport cover:
-->
<rect
id="canvas"
width="100%"
height="100%"
/>
<!--
The shape:
-->
<path
id="shoreline"
d="m144.7 126.2-2.8 8.8-3.9-2.3-2-7.7 1.7-4.3 5.5-4.4
1.5 9.9zm-51.2-102 6 6.3 4.4-1 7.5 6 1.9 1.1 2.5-.3
4 3.4 12.3 2.4-4.3 8.9-1.1 9.1-2.4 2.2-3.9-1.2.3
3.2-6.3 7-.1 5.6 4.1-1.9 2.9 5.4-.3 3.6 2.5 4.6-3
3.7 2.2 9.3 4.6 1.5-1 5.1-7.8 6.6-16.9-3.2-12.5
3.8-1 7-9.9 1.5-9.6-5.3-3.1 2.5-15.8-5.3-3.4-4.6
4.4-7.1L52.3 80l-8.8-13-6.3-6.4-13.1-4.9-.9-9.4
11.1-2.8L48.9 47l-2.7-14.8 8.1 5.7 20-10.3 2.6-11
7.5-2.8 1.3 4.8 4 .2 3.8 5.4z"
/>
</defs>
</svg>
(Credits:) See nice Drawing inner/outer strokes in SVG (clips and masks) article by Alex Chan demonstrating this technique.
Upvotes: 2
Reputation: 5948
I tried both of the existing answers and I found that they altered the shape of the inner contour in a messy way. My solution below covers the requirements except that the solution would ideally use an SVG filter. I used the clip-path
feature instead as it allowed me to preserve the correct miters and also not result in a blurry inset.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="300" viewbox="0 0 300 150">
<defs>
<clipPath id="inset1">
<path id="area1" d="M144.7,126.2l-2.8,8.8l-3.9-2.3l-2-7.7l1.7-4.3l5.5-4.4L144.7,
126.2z M93.5,24.2l6,6.3l4.4-1l7.5,6l1.9,1.1l2.5-0.3l4,3.4l
12.3,2.4l-4.3,8.9l-1.1,9.1l-2.4,2.2l-3.9-1.2l0.3,3.2l-6.3,7l
-0.1,5.6l4.1-1.9l2.9,5.4L121,84l2.5,4.6l-3,3.7l2.2,9.3l4.6,
1.5l-1,5.1l-7.8,6.6l-16.9-3.2l-12.5,3.8l-1,7l-9.9,1.5l-9.6
-5.3l-3.1,2.5l-15.8-5.3l-3.4-4.6l4.4-7.1l1.6-24.1l-8.8-13l
-6.3-6.4l-13.1-4.9l-0.9-9.4l11.1-2.8L48.9,47l-2.7-14.8l8.1,
5.7l20-10.3l2.6-11l7.5-2.8l1.3,4.8l4,0.2L93.5,24.2z"/>
</clipPath>
</defs>
<use href="#area1" stroke-width="6" fill="cyan" stroke="blue" clip-path="url(#inset1)"/>
<use href="#area1" stroke-width="2" fill="none" stroke="red"/>
</svg>
Upvotes: 4
Reputation: 31750
This does what you want. Please note that it depends on the stroke being a single color that's distinct from any of the fill colors (in this case 100% red - you can change the stroke color to anything you want but the filter gets more complicated).
You can adjust the color of the "fake" inner stroke by altering the values in the last column of the final feColorMatrix. Right now, it's 100% blue. (You can also use feMorphology to create this - as in def's answer - but that approach does not preserve the original mitering.)
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150" height="150" style="transform:scale(2);transform-origin:0 0 ">
<defs>
<filter id='fake-stroke' x='-50%' y='-50%' width='200%' height='200%' color-interpolation-filters="sRGB">
<!-- select just the red outline and zero out the opacity of everything that's not 100% red. -->
<feColorMatrix type="matrix" values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
255 -255 -255 -254 0" result="outline-only"/>
<feGaussianBlur stdDeviation="1"/>
<!-- select just the blur - not the original stroke. -->
<feComposite operator="out" in2="outline-only"/>
<!-- select just the blur that overlaps the original content -->
<feComposite operator="in" in2="SourceGraphic" />
<!-- increase its opacity to 100% except the most blurred - to fake anti-aliasing -->
<feComponentTransfer>
<feFuncA type="table" tableValues="0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1"/>
</feComponentTransfer>
<!-- change the color of the fake stroke to the desired value -->
<feColorMatrix type="matrix" values ="0 0 0 0 0
0 0 0 0 0
0 0 0 0 1
0 0 0 1 0"/>
<!-- put it on top of the original -->
<feComposite operator="over" in2="SourceGraphic"/>
</filter>
</defs>
<path class="st0" d="M144.7,126.2l-2.8,8.8l-3.9-2.3l-2-7.7l1.7-4.3l5.5-4.4L144.7,126.2z M93.5,24.2l6,6.3l4.4-1l7.5,6l1.9,1.1
l2.5-0.3l4,3.4l12.3,2.4l-4.3,8.9l-1.1,9.1l-2.4,2.2l-3.9-1.2l0.3,3.2l-6.3,7l-0.1,5.6l4.1-1.9l2.9,5.4L121,84l2.5,4.6l-3,3.7
l2.2,9.3l4.6,1.5l-1,5.1l-7.8,6.6l-16.9-3.2l-12.5,3.8l-1,7l-9.9,1.5l-9.6-5.3l-3.1,2.5l-15.8-5.3l-3.4-4.6l4.4-7.1l1.6-24.1
l-8.8-13l-6.3-6.4l-13.1-4.9l-0.9-9.4l11.1-2.8L48.9,47l-2.7-14.8l8.1,5.7l20-10.3l2.6-11l7.5-2.8l1.3,4.8l4,0.2L93.5,24.2z" stroke-width="2" fill="#00ffff" stroke="#FF0000" filter="url(#fake-stroke)"/>
</svg>
Upvotes: 7
Reputation: 5349
If you want clear shape, you should use SVG transform instead of applying CSS transform to svg element.
And when you draw "inside stroke", the feMorphorogy element is useful. This reduces(or increases) paint area of target shape, thus you can draw "fake inside/outside" stroke.
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300">
<defs>
<filter id='inset' x='-50%' y='-50%' width='200%' height='200%'>
<!--outside-stroke-->
<feFlood flood-color="red" result="outside-color"/>
<feMorphology in="SourceAlpha" operator="dilate" radius="2"/>
<feComposite in="outside-color" operator="in" result="outside-stroke"/>
<!--inside-stroke-->
<feFlood flood-color="blue" result="inside-color"/>
<feComposite in2="SourceAlpha" operator="in" result="inside-stroke"/>
<!--fill-area-->
<feMorphology in="SourceAlpha" operator="erode" radius="2"/>
<feComposite in="SourceGraphic" operator="in" result="fill-area"/>
<!--merge graphics-->
<feMerge>
<feMergeNode in="outside-stroke"/>
<feMergeNode in="inside-stroke"/>
<feMergeNode in="fill-area"/>
</feMerge>
</filter>
</defs>
<g transform="scale(2)">
<path class="st0" d="M144.7,126.2l-2.8,8.8l-3.9-2.3l-2-7.7l1.7-4.3l5.5-4.4L144.7,126.2z M93.5,24.2l6,6.3l4.4-1l7.5,6l1.9,1.1
l2.5-0.3l4,3.4l12.3,2.4l-4.3,8.9l-1.1,9.1l-2.4,2.2l-3.9-1.2l0.3,3.2l-6.3,7l-0.1,5.6l4.1-1.9l2.9,5.4L121,84l2.5,4.6l-3,3.7
l2.2,9.3l4.6,1.5l-1,5.1l-7.8,6.6l-16.9-3.2l-12.5,3.8l-1,7l-9.9,1.5l-9.6-5.3l-3.1,2.5l-15.8-5.3l-3.4-4.6l4.4-7.1l1.6-24.1
l-8.8-13l-6.3-6.4l-13.1-4.9l-0.9-9.4l11.1-2.8L48.9,47l-2.7-14.8l8.1,5.7l20-10.3l2.6-11l7.5-2.8l1.3,4.8l4,0.2L93.5,24.2z"
fill="#00ffff" filter="url(#inset)"/>
</g>
</svg>
Upvotes: 10