Background color of text in SVG
Asked Answered
V

13

151

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?

Volplane answered 19/3, 2013 at 13:30 Comment(6)
Can you share your code so far?Ullage
These also might help https://mcmap.net/q/117079/-svg-text-inside-rect and #8675723Ullage
#12260870 also shows how to do this using filters.Mendacity
@RobertLongson Closing this question as duplicate when it was asked 2 years prior to the other one seems wrong, especially when the only answer there is yours.Hexosan
@Aperçu: The age of a question is not the main factor when choosing a duplicate target, see for example here.Poindexter
@RobertLongson would you say this question is not of comparable (if not better) quality to the other one even though it has 42 upvotes against 8? Your answer also as the same score as the lower of these two, and less than a third of the top one. The quality of your answer is rather subjective to you and shouldn't really matter.Hexosan
I
121

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);
Itu answered 1/6, 2013 at 13:14 Comment(4)
That or use an svg filter (feFlood + feComposite) on the text. See slightly similar question #12260870.Mendacity
This solution using getBBox(), although it works just fine, can be quite slow when a large number of calculations need to be done. The issue with using an svg filter (feFlood + feComposite) is that the text comes out a little jagged. Have offered a simple, but hacky solution below.Chaperone
Better to use textElm = document.getElementById("the-text") instead of textElm = ctx.getElementById("the-text")?Spurious
How cal I use the same getBBox function in nodeJSUncanny
D
134

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>
Decurrent answered 23/6, 2015 at 21:3 Comment(12)
What does "SourceGraphic" mean here? Does "url(#solid)" actually cause an extra web access?Adjuvant
No, it shouldn’t. It’s referring to the definition of the <filter> in the <defs> tag, which is part of the svg (and hence not loaded again).Shelba
text is blurry here :(Hild
Can yo give the background padding?Bubo
@Bubo best aska separate question about that showing what you have so far.Decurrent
@RobertLongson - I've asked, here it isBubo
Love this solution in theory, but can confirm that the text is blurry. It seems like the filter breaks anti-aliasing.Gravedigger
Me too, Love the solution, got the +1, but definitely breaks anti aliasing :(Gotcher
Add operator="xor" to feComposite to prevent blurry text. @RobertLongson @Hild @Gravedigger @billMerideth
Is is possible to make borders rounded?Collectivity
Note: this doesn't work in Safari if you have a <base href="" /> tag in your html.Vizor
x="-10%" y="-10%" width="120%" height="120%" to add paddingMincey
I
121

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);
Itu answered 1/6, 2013 at 13:14 Comment(4)
That or use an svg filter (feFlood + feComposite) on the text. See slightly similar question #12260870.Mendacity
This solution using getBBox(), although it works just fine, can be quite slow when a large number of calculations need to be done. The issue with using an svg filter (feFlood + feComposite) is that the text comes out a little jagged. Have offered a simple, but hacky solution below.Chaperone
Better to use textElm = document.getElementById("the-text") instead of textElm = ctx.getElementById("the-text")?Spurious
How cal I use the same getBBox function in nodeJSUncanny
C
44

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!

Chaperone answered 27/1, 2017 at 19:58 Comment(2)
Also prints nicely where as the filter solution was very blurry when printed.Sensuous
Love it.. it looks awesome when you adjust stroke width a little.Magdamagdaia
S
27

Instead of using a <text> tag, the <foreignObject> tag can be used, which allows for XHTML content with CSS.

Spermiogenesis answered 10/1, 2018 at 18:58 Comment(4)
Can you leave an example?Ashjian
Underrated answer. developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject Contains an useful example. Within your foreignObject you can use divs & spans together with css definitions.Phox
Doesn't work in Safari if you want to apply a CSS transformation to a DIV inside a foreignObject within an SVG.Justin
foreignObject is a hack to the SVG world and not all browsers fully support it as @KernelJames mentioned.Erminois
T
18

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");
Thuggee answered 24/6, 2014 at 4:48 Comment(3)
This does not work; it only changes the color of the text, not the background color.Gnawing
Enclose the text in a div or span and apply styling to whichever of the last two you have used.Fontanez
This post explains it well: cambridge-intelligence.com/…Futurism
H
12

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.

Haehaecceity answered 17/2, 2022 at 15:16 Comment(1)
This is exactly what I wanted. Can be added to css without changing doc itself.Aguilera
F
8

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

Favouritism answered 14/3, 2017 at 10:24 Comment(4)
Concerning padding, you'd better use the parameters x, y, width and height of filter. The default values provide a nice padding (I mean, not the values of your answer, but the default SVG values which are taken if you do not define them).Haehaecceity
Using the operator "xor" makes the text appear as transparent instead of the chosen color (if we except the more complex case of a flood-color with alpha). Don't you want to use operator "over" instead?Haehaecceity
@Haehaecceity then why does the text have a color? and adding fill="red" on the text makes the text red?Prospector
@Prospector it's because the text is duplicated. What I meant is you can use only the first text element if you use operator over instead of xorHaehaecceity
S
4

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.

Sharecrop answered 5/4, 2020 at 11:28 Comment(0)
G
3

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> 
Geniculate answered 12/3, 2019 at 5:19 Comment(0)
F
2

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>
Fistic answered 19/9, 2017 at 20:10 Comment(0)
F
1

The previous answers relied on doubling up text and lacked sufficient whitespace.

By using atop and &nbsp; 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">&nbsp;130 cm&nbsp;</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">&nbsp;60 cm&nbsp;</text>
    </g>
</g>
</svg>
Fennelly answered 9/11, 2019 at 18:37 Comment(1)
The use &nbsp; instead of &#160; causes problems on many browsers as it doesn't validate as SVG. Also, one might want to add xmlns="http://www.w3.org/2000/svg" as an attribute to the svg tag to ensure the browser interprets this properly (e.g. this doesn't work on Firefox as-is).Fredrika
M
0

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.

Militarize answered 18/11, 2022 at 9:34 Comment(0)
I
-1

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 :)

Ineslta answered 1/10, 2018 at 18:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.