SVG reduced path with fill pattern - svg

I try to draw a shape with a path that must be reduced like in this solution Find Parallel or Offset SVG path
I use the filter "erode" but with pattern it doesn't work : the pattern is deformed.
Is there a way to do this without the bezier.js solution, with pure SVG/CSS ?
Here is a sample of my problem
I want to have the shape of the right with the pattern of the left.
<!DOCTYPE html>
<html>
<body>
<svg>
<defs>
<pattern id="circ" x="0" y="0" width="30" height="30" patternUnits="userSpaceOnUse">
<rect fill="blue" width="100%" height="100%" />
<circle cx="10" cy="10" r="10" fill="green" />
</pattern>
<filter id="erode">
<feMorphology in="SourceGraphic" operator="erode" radius="10"/>
</filter>
<path id="thing" d="M 0,0 H 50 A 35,35 0 1 0 100,50 V 75 C 50,125 0,85 0,85 Z" />
</defs>
<use href="#thing" fill="url(#circ)" width="400" height="400" filter="#erode"/>
<use x="100" href="#thing" filter="url(#erode)" fill="url(#circ)" width="400" height="400" />
</svg>
</body>
</html>

Yes. You can use a mask.
svg {
width: 300px;
}
.purple {
fill: rebeccapurple;
}
.reduce-me {
mask: url(#reducer);
}
#reduce-amount {
stroke-width: 5px;
}
<svg viewBox="0 0 100 100">
<path class="purple"
d="M 50,10 Q 100,10, 50,50 Q 0,90, 50,90
Q 100,90, 50,50 Q 0,10, 50,10 Z"/>
</svg>
<svg viewBox="0 0 100 100">
<defs>
<!-- the shared path that is used by both the purple path and the mask -->
<path id="shared-path"
id="p" d="M 50,10 Q 100,10, 50,50 Q 0,90, 50,90
Q 100,90, 50,50 Q 0,10, 50,10 Z" />
<!-- a mask that shrinks the shape by half the stroke-width -->
<mask id="reducer">
<use id="reduce-amount" xlink:href="#shared-path"
fill="white" stroke="black"/>
</mask>
</defs>
<!-- the shape that gets reduced -->
<use class="purple reduce-me" xlink:href="#shared-path"/>
</svg>
How this works
If we just render what the mask looks like (on the right) we can see how this works.
svg {
width: 300px;
}
.purple {
fill: rebeccapurple;
}
.reduce-me {
mask: url(#reducer);
}
#reduce-amount {
stroke-width: 10px;
}
<svg viewBox="0 0 100 100">
<path class="purple"
d="M 50,10 Q 100,10, 50,50 Q 0,90, 50,90
Q 100,90, 50,50 Q 0,10, 50,10 Z"/>
</svg>
<svg viewBox="0 0 100 100">
<defs>
<!-- the shared path that is used by both the purple path and the mask -->
<path id="shared-path"
id="p" d="M 50,10 Q 100,10, 50,50 Q 0,90, 50,90
Q 100,90, 50,50 Q 0,10, 50,10 Z" />
</defs>
<use id="reduce-amount" xlink:href="#shared-path"
fill="white" stroke="black"/>
</svg>
We are using the same shape as a mask. However the mask has a thick black stroke around it. Black in a mask makes things transparent. The rest of the mask is white, which stays visible.
You can alter the amount of the shape reduction by changing the stroke-width value in the .reduce-amount class.
The disadvantages of this method are:
1. you need a mask for every different path shape
2. you can't set the stroke style of the reduced size shape. However you could simulate a stroke colour by overlaying two paths with different reduction amounts.

You can extend the filter to make this work. There seems to be a bug eroding pattern filled shapes - it's not taking the minimum of the alpha channels in the radius correctly. But if you start with SourceAlpha rather than SourceGraphic and then create your mask using component transfers it seems to work.
<svg>
<defs>
<pattern id="circ" x="0" y="0" width="30" height="30" patternUnits="userSpaceOnUse">
<rect fill="blue" width="100%" height="100%" />
<circle cx="10" cy="10" r="10" fill="green" />
</pattern>
<filter id="erode3">
<feMorphology in="SourceAlpha" result="eroded"
operator="erode" radius="10"/>
<feComponentTransfer>
<feFuncR type="discrete" tableValues="1 0"/>
<feFuncG type="discrete" tableValues="1 0"/>
<feFuncB type="discrete" tableValues="1 0"/>
</feComponentTransfer>
<feComposite operator ="in" in="SourceGraphic"/>
</filter>
<path id="thing" d="M 0,0 H 50 A 35,35 0 1 0 100,50 V 75 C 50,125 0,85 0,85 Z" />
</defs>
<use href="#thing" fill="url(#circ)" width="400" height="400" filter="url(#erode3)"/>
<use x="100" href="#thing" filter="url(#erode)" fill="url(#circ)" width="400" height="400" />
</svg>

Related

How do I stop svg GaussianBlur from being clipped? [duplicate]

Why does the grid in this SVG not fill the entire 256x256 space? No matter what size I change it to, about 15% of the grid is cut off, which appears to me to be arbitrary in the context of my code.
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="grid" width="18.75" height="18.75" patternUnits="userSpaceOnUse">
<path d="M 18.75 0 L 0 0 0 18.75" fill="none" stroke="black" stroke-width="1"/>
</pattern>
<rect id="gridRect" width="100%" height="100%" fill="url(#grid)" />
<filter id="gridify" width="100%" height="100%" filterUnits = "userSpaceOnUse">
<feImage result="sourceTwo" xlink:href="#gridRect" />
<feComposite in="SourceGraphic" in2="sourceTwo" operator="in"/>
</filter>
</defs>
<g filter="url(#gridify)" >
<rect width="100%" height="100%" fill="url(#linGradient)" />
</g>
<rect width="100%" height="100%" fill="none" stroke="black" stroke-width="1"/>
</svg>
The SVG specification defaults filters to being 10% larger than the filtered object by default.
If ‘x’ or ‘y’ is not specified, the effect is as if a value of -10% were specified.
If ‘width’ or ‘height’ is not specified, the effect is as if a value of 120% were specified.
I imagine that's to stop lots of questions along the lines of "why is my Gaussian blur based drop-shadow filter cut off?"
So, looks like all I needed to do to fix it was add an x="0" and a y="0" to the filter. I don't understand why it is necessary though, as it does not make sense that it would default to "-15%".
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="grid" width="18.75" height="18.75" patternUnits="userSpaceOnUse">
<path d="M 18.75 0 L 0 0 0 18.75" fill="none" stroke="black" stroke-width="1"/>
</pattern>
<rect id="gridRect" width="100%" height="100%" fill="url(#grid)" />
<filter id="gridify" x="0" y="0" width="100%" height="100%" filterUnits = "userSpaceOnUse">
<feImage result="sourceTwo" xlink:href="#gridRect" />
<feComposite in="SourceGraphic" in2="sourceTwo" operator="in"/>
</filter>
</defs>
<g filter="url(#gridify)" >
<rect width="100%" height="100%" fill="url(#linGradient)" />
</g>
<rect width="100%" height="100%" fill="none" stroke="black" stroke-width="1"/>
</svg>

Best way to fill a SVG shape with other elements without clipping them at boundary?

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>

SVG path mask not working

According to example of this tutorials I want to mask my icon (black and white) instead of text in this tutorial. but right side of icon is still dark. how can I make left side of my icon black stroke and right side of icon goes white stroke?
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="41px" height="50px" viewbox="-3 -3 40 48">
<defs>
<mask id="myMask" maskUnits="userSpaceOnUse"
x="0" y="0" width="18" height="50">
<rect x="0" y="0" width="15" height="50" fill="white"/>
</mask>
<path id="fire-icon" d="m 25.55134,17.573125 c 0.647761,1.273107 1.136636,2.596633 1.344412,4.021007 0.21999,1.47479 0.13444,2.924371 -0.342217,4.348739 -0.427768,1.273111 -1.124413,2.382358 -2.004391,3.378155 -0.977758,1.096637 -2.114394,1.991591 -3.348802,2.773105 -0.183328,0.113445 -0.366658,0.226895 -0.57443,0.365546 1.429964,-3.214282 1.686623,-5.760501 0.366659,-8.924365 -0.904421,-2.155462 -2.395495,-3.743696 -3.519909,-4.4874 0,0 0.305545,3.655466 -2.248829,5.9874 0.391098,-1.184876 -0.672208,-2.31933 -0.672208,-2.31933 -0.31777,1.386549 -0.8922,1.651268 -1.662178,2.836135 -0.366658,0.567227 -0.708873,1.159666 -0.941085,1.802516 -0.342214,0.970596 -0.403325,1.953785 -0.207778,2.96219 0.134444,0.668067 0.342218,1.310925 0.63554,1.941174 0.02446,0.05042 0.04889,0.113449 0.08556,0.226893 C 12.156129,32.308418 11.899468,32.169763 11.655035,31.993294 10.921719,31.463881 10.163961,30.959677 9.467313,30.37985 8.5628918,29.623547 7.8173555,28.715982 7.3284794,27.619344 7.0351536,26.951277 6.8640469,26.245395 6.8151593,25.514301 c -0.097775,-1.65126 0.2933257,-3.201685 0.9899743,-4.676472 0.5010979,-1.071427 0.9777525,-2.142852 1.4544067,-3.226887 0.1344411,-0.31513 0.2199942,-0.66807 0.3055478,-1.008411 0.085554,-0.327726 0.1344412,-0.668063 0.1955503,-1.033609 0.9777516,0.46639 1.4544056,2.521009 0.9044216,3.857144 0.02444,0 0.03666,0 0.06111,0 0.232216,-0.302517 0.464431,-0.605039 0.696652,-0.907563 0.977751,-1.323526 1.869945,-2.684872 2.566595,-4.184872 0.623316,-1.34874 1.099973,-2.735295 1.185527,-4.247902 0.02445,-0.5294123 -0.02445,-1.0714263 -0.03667,-1.6008383 0.01223,0 0.02445,0 0.03667,0 0.65998,0.315125 1.307741,0.680673 1.882172,1.134457 1.014423,0.8193243 1.857728,1.7899163 2.566601,2.8991563 0.892198,1.411766 1.515512,2.936975 1.955505,4.550418 0.01223,0.06302 0.03667,0.126055 0.06111,0.214286 1.344407,-1.210083 0.513317,-4.172266 0.501096,-4.222688 -0.02445,-0.0252 2.261053,2.029411 3.409911,4.512605 z"
fill="none" stroke="#303233" stroke-width="1.3" />
</defs>
<!-- Draw black rectangle in the background -->
<rect x="18" y="0" width="15" height="50" fill="#000" />
<!-- Draw the text string twice. First, the white text without mask.
Second, the black text with the mask applied-->
<use xlink:href="#fire-icon" stroke="white"/>
<use xlink:href="#fire-icon" stroke="black" mask="url(#myMask)"/>
</svg>
There's no mask issue. The stroke on the path overrides the stroke on the use element, so in the question there are two grey (#303233) stroked paths. Removing the path stroke atribute allows the use to set one.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="41px" height="50px" viewbox="-3 -3 40 48">
<defs>
<mask id="myMask" maskUnits="userSpaceOnUse"
x="0" y="0" width="18" height="50">
<rect x="0" y="0" width="15" height="50" fill="white"/>
</mask>
<path id="fire-icon" d="m 25.55134,17.573125 c 0.647761,1.273107 1.136636,2.596633 1.344412,4.021007 0.21999,1.47479 0.13444,2.924371 -0.342217,4.348739 -0.427768,1.273111 -1.124413,2.382358 -2.004391,3.378155 -0.977758,1.096637 -2.114394,1.991591 -3.348802,2.773105 -0.183328,0.113445 -0.366658,0.226895 -0.57443,0.365546 1.429964,-3.214282 1.686623,-5.760501 0.366659,-8.924365 -0.904421,-2.155462 -2.395495,-3.743696 -3.519909,-4.4874 0,0 0.305545,3.655466 -2.248829,5.9874 0.391098,-1.184876 -0.672208,-2.31933 -0.672208,-2.31933 -0.31777,1.386549 -0.8922,1.651268 -1.662178,2.836135 -0.366658,0.567227 -0.708873,1.159666 -0.941085,1.802516 -0.342214,0.970596 -0.403325,1.953785 -0.207778,2.96219 0.134444,0.668067 0.342218,1.310925 0.63554,1.941174 0.02446,0.05042 0.04889,0.113449 0.08556,0.226893 C 12.156129,32.308418 11.899468,32.169763 11.655035,31.993294 10.921719,31.463881 10.163961,30.959677 9.467313,30.37985 8.5628918,29.623547 7.8173555,28.715982 7.3284794,27.619344 7.0351536,26.951277 6.8640469,26.245395 6.8151593,25.514301 c -0.097775,-1.65126 0.2933257,-3.201685 0.9899743,-4.676472 0.5010979,-1.071427 0.9777525,-2.142852 1.4544067,-3.226887 0.1344411,-0.31513 0.2199942,-0.66807 0.3055478,-1.008411 0.085554,-0.327726 0.1344412,-0.668063 0.1955503,-1.033609 0.9777516,0.46639 1.4544056,2.521009 0.9044216,3.857144 0.02444,0 0.03666,0 0.06111,0 0.232216,-0.302517 0.464431,-0.605039 0.696652,-0.907563 0.977751,-1.323526 1.869945,-2.684872 2.566595,-4.184872 0.623316,-1.34874 1.099973,-2.735295 1.185527,-4.247902 0.02445,-0.5294123 -0.02445,-1.0714263 -0.03667,-1.6008383 0.01223,0 0.02445,0 0.03667,0 0.65998,0.315125 1.307741,0.680673 1.882172,1.134457 1.014423,0.8193243 1.857728,1.7899163 2.566601,2.8991563 0.892198,1.411766 1.515512,2.936975 1.955505,4.550418 0.01223,0.06302 0.03667,0.126055 0.06111,0.214286 1.344407,-1.210083 0.513317,-4.172266 0.501096,-4.222688 -0.02445,-0.0252 2.261053,2.029411 3.409911,4.512605 z"
fill="none" stroke-width="1.3" />
</defs>
<!-- Draw black rectangle in the background -->
<rect x="18" y="0" width="15" height="50" fill="#000" />
<!-- Draw the text string twice. First, the white text without mask.
Second, the black text with the mask applied-->
<use xlink:href="#fire-icon" stroke="white"/>
<use xlink:href="#fire-icon" stroke="black" mask="url(#myMask)"/>
</svg>

How to start svg pattern from begin for two elements separately and how to setup right coordinate system?

I have two thick lines and I want to apply pattern for this lines. Lines should have the same pattern, but start of drawing pattern should start from (0, 0) for each line separately. In my experiment http://jsfiddle.net/69t09wey/ patterns apply like mask. I.e pattern apply for whole svg canvas as invisible layer and where line is visible, pattern also visible.
<svg viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<pattern id="pattern-1" width="20" height="20" x="0" y="0" patternUnits = "userSpaceOnUse" >
<path d="M 0 0 L 20 20" fill="none" stroke="#0000ff" stroke-width="1"></path>
</pattern>
<g transform="scale(5)">
<rect x="1" y="1" width="1000" height="1000"
fill="none" stroke="blue" />
<path d="M 1 9 L 200 9"
fill="red" stroke="url(#pattern-1)" stroke-width="20" />
<path d="M 1 53 L 200 53"
fill="red" stroke="url(#pattern-1)" stroke-width="20" />
</g>
</svg>
If you make your lines the same. Then move the second one by applying a transform. That will shift the coordinate space of the pattern.
<svg viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<pattern id="pattern-1" width="20" height="20" x="0" y="0" patternUnits = "userSpaceOnUse" >
<path d="M 0 0 L 20 20" fill="none" stroke="#0000ff" stroke-width="1"></path>
</pattern>
<g transform="scale(5)">
<rect x="1" y="1" width="1000" height="1000"
fill="none" stroke="none" />
<path d="M 1 9 L 200 9"
fill="red" stroke="url(#pattern-1)" stroke-width="20" />
<path d="M 1 9 L 200 9" transform="translate(0,44)"
fill="red" stroke="url(#pattern-1)" stroke-width="20" />
</g>
</svg>

SVG: draw text with solid background color [duplicate]

I want to color the background of svg text similar to background-color in css
I was only able to find documentation on fill, which colors the text itself
Is it even possible?
You could use a filter to generate the background.
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" result="bg" />
<feMerge>
<feMergeNode in="bg"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">solid background</text>
</svg>
No this is not possible, SVG elements do not have background-... presentation attributes.
To simulate this effect you could draw a rectangle behind the text attribute with fill="green" or something similar (filters). Using JavaScript you could do the following:
var ctx = document.getElementById("the-svg"),
textElm = ctx.getElementById("the-text"),
SVGRect = textElm.getBBox();
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", SVGRect.x);
rect.setAttribute("y", SVGRect.y);
rect.setAttribute("width", SVGRect.width);
rect.setAttribute("height", SVGRect.height);
rect.setAttribute("fill", "yellow");
ctx.insertBefore(rect, textElm);
The solution I have used is:
<svg>
<line x1="100" y1="100" x2="500" y2="100" style="stroke:black; stroke-width: 2"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.6em">Hello World!</text>
<text x="150" y="105" style="fill:black">Hello World!</text>
</svg>
A duplicate text item is being placed, with stroke and stroke-width attributes. The stroke should match the background colour, and the stroke-width should be just big enough to create a "splodge" on which to write the actual text.
A bit of a hack and there are potential issues, but works for me!
Instead of using a <text> tag, the <foreignObject> tag can be used, which allows for XHTML content with CSS.
No, you can not add background color to SVG elements. You can do it programmatically with d3.
var text = d3.select("text");
var bbox = text.node().getBBox();
var padding = 2;
var rect = self.svg.insert("rect", "text")
.attr("x", bbox.x - padding)
.attr("y", bbox.y - padding)
.attr("width", bbox.width + (padding*2))
.attr("height", bbox.height + (padding*2))
.style("fill", "red");
Answer by Robert Longson (#RobertLongson) with modifications:
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow"/>
<feComposite in="SourceGraphic" operator="xor"/>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50"> solid background </text>
<text x="20" y="50" font-size="50">solid background</text>
</svg>
and we have no bluring and no heavy "getBBox" :)
Padding is provided by white spaces in text-element with filter.
It's worked for me
Going further with #dbarton_uk answer, to avoid duplicating text you can use paint-order=stroke style:
<svg>
<line x1="100" y1="100" x2="350" y2="100" style="stroke:grey; stroke-width: 100"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.5em; fill:black; paint-order:stroke; stroke-linejoin:round">Hello World!</text>
</svg>
Note the stroke-linejoin:round which is needed to avoid seeing spikes for the W sharp angle.
You can combine filter with the text.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>SVG colored patterns via mask</title>
</head>
<body>
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter x="0" y="0" width="1" height="1" id="bg-text">
<feFlood flood-color="white"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<!-- something has already existed -->
<rect fill="red" x="150" y="20" width="100" height="50" />
<circle cx="50" cy="50" r="50" fill="blue"/>
<!-- Text render here -->
<text filter="url(#bg-text)" fill="black" x="20" y="50" font-size="30">text with color</text>
<text fill="black" x="20" y="50" font-size="30">text with color</text>
</svg>
</body>
</html>
this is my favorite hack (not sure it should work). It refer an element that is not yet displayed, and it works pretty well
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 620 40" preserveAspectRatio="xMidYMid meet">
<defs>
<filter x="-0.02" y="0" width="1.04" height="1.1" id="removebackground">
<feFlood flood-color="#00ffff"/>
</filter>
</defs>
<!--Draw the text-->
<use xlink:href="#mygroup" filter="url(#removebackground)" />
<g id="mygroup">
<text id="text1" x="9" y="20" style="text-anchor:start;font-size:14px;">custom text with background</text>
<line x1="200" y1="18" x2="200" y2="36" stroke="#000" stroke-width="5"/>
<line x1="120" y1="27" x2="203" y2="27" stroke="#000" stroke-width="5"/>
</g>
</svg>
For those wondering how to apply padding to a text element when it has a background like in the Robert's answer, do the following:
<svg>
<defs>
<filter x="-0.1" y="-0.1" width="1.2" height="1.2" id="solid">
<feFlood flood-color="#171717"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">Hello</text>
</svg>
In the example above, filter's x and y positions can be used as transform: translate(-10%, -10%) would, and width and height values can be read as 120% and 120%. So we made background 20% bigger, and offsetted it -10%, so background is now 10% bigger on each side of the text.
The previous answers relied on doubling up text and lacked sufficient whitespace.
By using atop and I was able to get the results I wanted.
This example also includes arrows, a common use case for SVG text labels:
<svg viewBox="-105 -40 210 234">
<title>Size Guide</title>
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="white"></feFlood>
<feComposite in="SourceGraphic" operator="atop"></feComposite>
</filter>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z"></path>
</marker>
</defs>
<g id="garment">
<path id="right-body" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 0 l30 0 l0 154 l-30 0"></path>
<path id="right-sleeve" d="M30 0 l35 0 l0 120 l-35 0" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></path>
<use id="left-body" href="#right-body" transform="scale(-1,1)"></use>
<use id="left-sleeve" href="#right-sleeve" transform="scale(-1,1)"></use>
<path id="collar-right-top" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 -6.5 l11.75 0 l6.5 6.5"></path>
<use id="collar-left-top" href="#collar-right-top" transform="scale(-1,1)"></use>
<path id="collar-left" fill="white" stroke="black" stroke-width="1" stroke-linejoin="round" d="M-11.75 -6.5 l-6.5 6.5 l30 77 l6.5 -6.5 Z"></path>
<path id="front-right" fill="white" stroke="black" stroke-width="1" d="M18.25 0 L30 0 l0 154 l-41.75 0 l0 -77 Z"></path>
<line x1="0" y1="0" x2="0" y2="154" stroke="black" stroke-width="1" stroke-dasharray="1 3"></line>
<use id="collar-right" href="#collar-left" transform="scale(-1,1)"></use>
</g>
<g id="dimension-labels">
<g id="dimension-sleeve-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="85" y1="0" x2="85" y2="120" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="85" y="60" class="dimension" text-anchor="middle" dominant-baseline="middle"> 120 cm</text>
</g>
<g id="dimension-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-85" y1="0" x2="-85" y2="154" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="-85" y="77" text-anchor="middle" dominant-baseline="middle" class="dimension"> 154 cm</text>
</g>
<g id="dimension-sleeve-to-sleeve">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-65" y1="-20" x2="65" y2="-20" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="-20" text-anchor="middle" dominant-baseline="middle" class="dimension"> 130 cm </text>
</g>
<g title="Back Width" id="dimension-back-width">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-30" y1="174" x2="30" y2="174" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="174" text-anchor="middle" dominant-baseline="middle" class="dimension"> 60 cm </text>
</g>
</g>
</svg>
An obvious workaround to the problem of the blur produced by the filter effect is to render the <text> two times: once for the background (with transparent characters) and once for the characters (without a background filter).
For me, this was the only way to make the text readable in Safari.
<svg width="100%" height="100%">
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" />
</filter>
<g transform="translate(20, 50)" font-size="50">
<text aria-hidden="true" fill="none" filter="url(#solid)">solid background</text>
<text fill="blue">solid background</text>
</g>
</svg>
The aria-hidden="true" attribute is there to prevent screen readers from speaking the text twice, if the user uses a screen reader.
You can add style to your text:
style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
text-shadow: rgb(255, 255, 255) -2px -2px 0px, rgb(255, 255, 255) -2px 2px 0px,
rgb(255, 255, 255) 2px -2px 0px, rgb(255, 255, 255) 2px 2px 0px;"
White, in this example.
Does not work in IE :)

Resources