Nick Ginanto
Nick Ginanto

Reputation: 32120

Background color of text in SVG

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?

Upvotes: 157

Views: 160323

Answers (14)

NotTheDr01ds
NotTheDr01ds

Reputation: 20630

When using a monospace font, here's an easy solution inspired somewhat by dbarton_uk's answer. Similar to that answer, it repeats two lines in the same location. I prefer the approach in that answer because, unlike most other answers here, it greatly simplifies (if not avoids) calculating the position at which to draw a background (either manually or via JavaScript).

Where this diverges from the above answer, however, is that rather than specifying the same X/Y, this approach uses a dy="0" to draw the next (foreground) line on top of the previous (background) one. The dy for background lines (or lines without any background) is simply your desired line-height.

Since we're using a monospace font, we can specify the background "x" location by simply filling with spaces up to that point, then create the background itself using a Unicode "full-block" character (U+2588) equal in width to the backgrounded text.

<svg>
<style>
tspan {
  white-space: pre;
  font-size: 16pt;
}
</style>
<text y="1em" font-family="monospace" font-size="14">
<tspan x="10" dy="1.2em"><tspan>   </tspan><tspan>     </tspan><tspan fill="rgb(205,0,0)">███</tspan>  </tspan>
<tspan x="10" dy="0"><tspan fill="rgb(205,0,0)">Red</tspan><tspan>     </tspan><tspan fill="rgb(229,229,229)" background="rgb(205,0,0)">Red</tspan>  </tspan>
<tspan x="10" dy="1.2em"><tspan>     </tspan><tspan>   </tspan><tspan fill="rgb(0,205,0)">█████</tspan></tspan>
<tspan x="10" dy="0"><tspan fill="rgb(0,205,0)">Green</tspan><tspan>   </tspan><tspan fill="rgb(229,229,229)" background="rgb(0,205,0)">Green</tspan></tspan>
<tspan x="10" dy="1.2em"><tspan>    </tspan><tspan>    </tspan><tspan fill="rgb(0,0,238)">████</tspan> </tspan>
<tspan x="10" dy="0"><tspan fill="rgb(0,0,238)">Blue</tspan><tspan>    </tspan><tspan fill="rgb(229,229,229)" background="rgb(0,0,238)">Blue</tspan> </tspan>
</text>
</svg>

Upvotes: 0

Roman Belov
Roman Belov

Reputation: 159

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

Upvotes: 8

momciloo
momciloo

Reputation: 927

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.

Upvotes: 4

Henry&#39;s Cat
Henry&#39;s Cat

Reputation: 806

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>

Upvotes: 1

GOTO 0
GOTO 0

Reputation: 47614

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.

Upvotes: 1

FlorianT
FlorianT

Reputation: 1227

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.

Upvotes: 12

Robert Longson
Robert Longson

Reputation: 123985

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>

Upvotes: 137

dbarton_uk
dbarton_uk

Reputation: 1067

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!

Upvotes: 47

Vu Phan
Vu Phan

Reputation: 584

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> 

Upvotes: 3

Jan Pi
Jan Pi

Reputation: 167

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

Upvotes: -2

Chris G
Chris G

Reputation: 501

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

Upvotes: 27

Calimero100582
Calimero100582

Reputation: 852

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>

Upvotes: 2

sluijs
sluijs

Reputation: 4217

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

Upvotes: 122

nnattawat
nnattawat

Reputation: 716

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");

Upvotes: 19

Related Questions