Imagine we have a long list of instances (as an example, see just two such instances (INSTANCE1 and INSTANCE2) below) which all depend on some pre-defined #DEFINED_RECTANGLE_WHITE which does not contain stroke information in its definition.
As you can see, some of the instances will be scaled (see e.g. scale(1 2)). I am wondering what would be the best way of now stroking all of these instances (all should have same stroke-width, I do not want the strokes to be wider in any dimension on the scaled objects).
<use id="INSTANCE1"
xlink:href="#DEFINED_RECTANGLE_WHITE"
transform="rotate(90, 1, 1) translate(10,-400) scale(1 2)">
</use>
<use id="INSTANCE2"
xlink:href="#DEFINED_RECTANGLE_WHITE"
transform="translate(10,140)">
</use>
How to do this without SVG1.2 features?
Could you use a filter to create an outline effect? You would have to enclose your use elements in a group or another svg and apply the filter to that.
Also, the dilate operator can have undesired results, like the bevel effect in the last rotated rectangle.
<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px" viewBox="0 0 100 100">
<defs>
<rect id="rectangle" width="10" height="10" fill="rgb(200,220,120)"/>
<filter id="outline">
<feMorphology operator="dilate" radius="4" result="result1"/>
<feFlood flood-color="rgb(51,51,51)" result="result2"/>
<feComposite in="result2" in2="result1" operator="in" result="result3"/>
<feComposite in="SourceGraphic" in2="result3"/>
</filter>
</defs>
<g filter="url(#outline)">
<use href="#rectangle" transform="translate(50,10) scale(4 1) rotate(90,1,1)"/>
<use href="#rectangle" transform="translate(40,30)"/>
<use href="#rectangle" transform="translate(20,50) scale(6 0.5)"/>
<use href="#rectangle" transform="translate(60,70) rotate(45,1,1)"/>
</g>
</svg>
edit : oh snap! #Kaiido posted exactly the same answer while I was writing mine. Sorry for stealing your thunder =(
I need to create several SVG shapes (like circles or polygons) and fill each one of them with some SVG elements.
One way to achieve this is to define the shape and apply a pattern with the fill attribute (cf. snippet below). However, the inner SVG elements are clipped by the outer shape's boundary (the triangles are clipped by the circle).
I would like to find a way to hide all the triangles that do not intersect with the circle, and leave the triangles that overflow at the boundary intact.
Please note that computing whether an element intersects with a circle is quite easy in Javascript but I need to create shapes with complicated boundaries such as polygons.
<!-- Learn about this code on MDN: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern -->
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
</defs>
<circle cx="60" cy="60" r="50" fill="url(#Triangle)"/>
</svg>
This is my sample code, but it is a bit tricky (uses pattern and mask and filter).
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
<!--pattern for filling-->
<pattern id="Triangle" width="10" height="10"
patternUnits="userSpaceOnUse">
<polygon points="5,0 10,10 0,10"/>
</pattern>
<!--mask by paint area-->
<mask id="Mask" maskUnits="userSpaceOnUse">
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
</mask>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<!--fill all screen by pattern and mask by paint area-->
<rect fill="url(#Triangle)" width="100%" height="100%" mask="url(#Mask)"/>
</svg>
So I explain it step by step.
1) find "hit area"(center points of pattern shapes included by paint area of base shape) by using dot pattern filling.
stroke-width is used to enlarge base shape.
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="none" stroke="red"/>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5"/>
</svg>
2) fatten the hit area by filter. This is pattern area.
feConvolveMatrix is used to make box size to be even.
10px(pattern unit size) = 1px(dot size) + 4 * 2px(feMorphology) + 1px(feConvolveMatrix)
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:gray;">
<!--common settings-->
<defs>
<!--pattern to detect shape area-->
<pattern id="Dots" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect x="4" y="4" width="1" height="1" fill="white"/>
</pattern>
<!--filter to find area in which pattern shapes must be painted-->
<filter id="Range">
<feMorphology radius="4" operator="dilate"/>
<feComponentTransfer>
<feFuncA type="linear" slope="255"/>
</feComponentTransfer>
<feConvolveMatrix order="2 2" kernelMatrix="1 1 1 1" divisor="1"/>
</filter>
</defs>
<!--paint structure-->
<defs>
<!--base shape-->
<circle id="Base" cx="60" cy="60" r="50"/>
</defs>
<use xlink:href="#Base" fill="url(#Dots)" stroke="url(#Dots)" stroke-width="5" filter="url(#Range)"/>
<use xlink:href="#Base" fill="none" stroke="red"/>
</svg>
3) finally , spread rect element to all svg area, and fill it by pattern shapes and mask it by pattern area created at 2).
This answer does not help the OP now. But I am posting this anyway as it might help future readers.
The following Javascript function should fill any shape with any pattern. It uses the new SVG2 SVGSVGElement.checkIntersection() method.
Unfortunately, checkIntersection() doesn't work properly yet in any browsers. The method call works in Chrome, but it doesn't perform the intersection test properly. The other browsers haven't even implemented the method.
function fillShapeWithPattern(shapeId, patternId)
{
var shape = document.getElementById(shapeId);
var pattern = document.getElementById(patternId);
var svg = shape.ownerSVGElement;
var shapeBounds = shape.getBBox();
var patternBounds = pattern.getBBox();
if (patternBounds.width == 0 || patternBounds.height == 0)
return; // Avoid infinite loops
// To simplify the intersection test, let's adjust the shape bounding
// boxe so that we can pretend the pattern box is at (0,0).
shapeBounds.x -= patternBounds.x;
shapeBounds.y -= patternBounds.y;
// An SVGRect object that we need for the intersection test
var testRect = svg.createSVGRect();
testRect.width = patternBounds.width;
testRect.height = patternBounds.height;
// Loop through a grid checking whether a rectangle representing
// the bounding box of the pattern, intersect with the shape
for (var y = shapeBounds.y;
y < (shapeBounds.y + shapeBounds.height);
y += patternBounds.height)
{
testRect.y = y + patternBounds.y;
for (var x = shapeBounds.x;
x < (shapeBounds.x + shapeBounds.width);
x += patternBounds.width)
{
testRect.x = x + patternBounds.y;
if (svg.checkIntersection(shape, testRect))
{
// Add a copy of the pattern shape to the SVG, by creating
// a <use> element at the right coordinates
var use = document.createElementNS(svg.namespaceURI, "use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#"+patternId);
use.setAttribute("x", x);
use.setAttribute("y", y);
svg.appendChild(use);
}
}
}
}
fillShapeWithPattern("shape", "pattern");
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<polygon id="pattern" points="5,0 10,10 0,10"/>
</defs>
<circle id="shape" cx="60" cy="60" r="50" fill="none" stroke="red"/>
</svg>
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>
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>
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>
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.
defghi1977's solution results in odd outline contours that do not match the original shape.
Michael Mullany's solution results in a good outer contour but the inner one is heavily blurred.
My solution results in sharp contours and correct miters.
SVG markup
<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>
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 mask="url(#beach)" fill="darkblue" opacity=".6" href="#canvas" />
<use mask="url(#cliffs)" fill="red" opacity=".4" href="#canvas" />
<use mask="url(#inland)" fill="lime" opacity=".3" href="#canvas" />
<defs>
<!-- Both "stroke halves" in 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.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"
/>
</defs>
</svg>
(Credits:) See nice Drawing inner/outer strokes in SVG (clips and masks) article by Alex Chan demonstrating this technique.