How to place and center text in an SVG rectangle
Asked Answered
P

12

363

I have the following rectangle:

<rect x="0px" y="0px" width="60px" height="20px"/>

I would like to center the word "Fiction" inside of it. For other rectangles, does SVG word wrap to stay within them? I can't seem to find anything specifically about inserting text within shapes that are centered both horizontally and vertically and word wrap. Also, the text can not leave the rectangle.

Looking at the https://www.w3.org/TR/SVG/text.html#TextElement example doesn't help since the text element's x and y are different from the rectangle's x and y. There doesn't seem to be width and height for text elements. I am not sure of the math here.

(My HTML table is just not going to work.)

Prober answered 5/4, 2011 at 2:0 Comment(0)
H
741

An easy solution to center text horizontally and vertically in SVG:

  1. Set the position of the text to the absolute center of the element in which you want to center it:

    • If it's the parent, you could just do x="50%" y ="50%".
    • If it's another element, x would be the x of that element + half its width (and similar for y but with the height).
  2. Use the text-anchor property to center the text horizontally with the value middle:

    middle

    The rendered characters are aligned such that the geometric middle of the resulting rendered text is at the initial current text position.

  3. Use the dominant-baseline property to center the text vertically with the value middle (or depending on how you want it to look like, you may want to do central)

Here is a simple demo:

<svg width="200" height="100">
  <rect x="0" y="0" width="200" height="100" stroke="red" stroke-width="3px" fill="white"/>
  <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">TEXT</text>    
</svg>

You can also use this with CSS if you want to apply it to many elements. For example:

svg text{
  text-anchor: middle;
  dominant-baseline: middle;
}
Hawsepiece answered 20/7, 2015 at 16:50 Comment(15)
I have been looking everywhere for a solution this simple - nothing else was working for me I used this for centering a number inside a radial progress barCurzon
In my case, it is the dominant-baseline property that does the job, not the alignment-baseline.Insulator
Unfortunately, this solution clips the rectangle stroke. See my example below for a solution that avoids clipping: https://mcmap.net/q/92483/-how-to-place-and-center-text-in-an-svg-rectangleImplement
If you’re going to do this regularly, you can also add the following: <style type="text/css"><![CDATA[ text { alignment-baseline: middle; text-anchor:middle; } ]]></style>. Thanks for the solution.Dharana
Doesn't work for me: The text is NOT centered vertically.Rhodolite
@RegisMay could you share the svg code so we could see the problem? Did you try dominant-baseline as suggested by other user in the comments?Hawsepiece
Run your own code snippet. It doesn't work. Check it out: It even doesn't work in the preview provided by stackoverflow itself. As stated before in the comment dominant-baseline indeed needs to be used. I'm sorry, but this answer is somehow misleading. As it doesn't seem to work in Firefox nor Chromium and in addition you must study the comments in order to find the correct solution on your own I took the liberty of adding another answer with code that works as expected. (For the records: Tested with FF and Chromium on Ubuntu Linux.)Rhodolite
@RegisMay thanks for pointing it out. I am on Chrome on Mac and it worked fine with alignment-baseline, but it seems that it may be a quirky thing because reviewing the specs, it should be dominant-baseline for text, and alignment-baseline for tspan, tref, altGlyph, and textPath. I updated the answer accordingly.Hawsepiece
Interesting, that there are these differences.Rhodolite
Thanks, x="50%" text-anchor="middle" worked for me.Collator
Why exactly are x="50%" AND text-anchor="middle" BOTH required?Delate
dominant-baseline="central" gave the look I wanted, FWIWStately
text-anchor with an x value works perfectly, thank you!Andrel
@Insulator I observed that dominant-baseline is working fine in Firefox, while alignment-baseline in ChromeEsurient
@Delate x="50%" sets the position of the text box in the SVG or g element, while text-anchor="middle" sets where on the text box the x value, 50%, is measured from. Without text-anchor="middle" the text will begin from the center of the parent, which will draw it to the right of the center. Without x="50%" the text box will be drawn at 0 on the X-axes, which is the far left. So you need both.Helles
L
58

If you are creating the SVG programmatically you can simplify it and do something like this:

  <g>
      <rect x={x} y={y} width={width} height={height} />
      <text
          x={x + width / 2}
          y={y + height / 2}
          dominant-baseline="middle"
          text-anchor="middle"
      >
          {label}
      </text>
  </g>
Liberticide answered 4/2, 2019 at 20:3 Comment(1)
I just could not get this to work, although changing the Y to use 1.5* instead of 2* yields perfectly centered text for me: y={y + height / 1.5}Indurate
O
41

SVG 1.2 Tiny added text wrapping, but most implementations of SVG that you will find in the browser (with the exception of Opera) have not implemented this feature. It's typically up to you, the developer, to position text manually.

The SVG 1.1 specification provides a good overview of this limitation, and the possible solutions to overcome it:

Each ‘text’ element causes a single string of text to be rendered. SVG performs no automatic line breaking or word wrapping. To achieve the effect of multiple lines of text, use one of the following methods:

  • The author or authoring package needs to pre-compute the line breaks and use multiple ‘text’ elements (one for each line of text).
  • The author or authoring package needs to pre-compute the line breaks and use a single ‘text’ element with one or more ‘tspan’ child elements with appropriate values for attributes ‘x’, ‘y’, ‘dx’ and ‘dy’ to set new start positions for those characters who start new lines. (This approach allows user text selection across multiple lines of text -- see Text selection and clipboard operations.)
  • Express the text to be rendered in another XML namespace such as XHTML [XHTML] embedded inline within a ‘foreignObject’ element. (Note: the exact semantics of this approach are not completely defined at this time.)

http://www.w3.org/TR/SVG11/text.html#Introduction

As a primitive, text wrapping can be simulated by using the dy attribute and tspan elements, and as mentioned in the spec, some tools can automate this. For example, in Inkscape, select the shape you want, and the text you want, and use Text -> Flow into Frame. This will allow you to write your text, with wrapping, which will wrap based on the bounds of the shape. Also, make sure you follow these instructions to tell Inkscape to maintain compatibility with SVG 1.1: https://wiki.inkscape.org/wiki/Frequently_asked_questions#What_about_flowed_text.3F

Furthermore, there are some JavaScript libraries that can be used to dynamically automate text wrapping: https://old.carto.net/papers/svg/textFlow/

It's interesting to note CSVG's solution to wrapping a shape to a text element (e.g. see their "button" example), although it's important to mention that their implementation is not usable in a browser: https://users.monash.edu/~clm/csvg/about.html

I'm mentioning this because I have developed a CSVG-inspired library that allows you to do similar things and does work in web browsers, although I haven't released it yet.

Occasionally answered 5/4, 2011 at 4:10 Comment(9)
Thanks for the reply...but ACK! It appears that I will have to dump svg then. One of the first things the developers should have thought of was attaching text (formatted as the users wish) to shapes! I will wait until they get their act together before I do anymore with it. I will try to redraw it in Windows Paint and see if that will do it. (If only I could get the browsers to display the table correctly.)Prober
About editable text in svg, Opera supports the editable attribute from SVG 1.2 Tiny, which makes it possible to get editable text by simply adding an attribute editable="true" to the text or textArea element.Overanxious
@Erik, once again Opera is ahead of the curve.Occasionally
@Lady Aleena, why not just use the Inkscape solution I mentinoed? If it's just a static image (e.g. no dynamic behaviour, no updating text content dynamically), then this should work fine. Plus, it will be scalable, which Paint will not, as Paint produces raster graphics. Finally, I don't believe that Paint supports text wrapping on any level.Occasionally
@echo-flow I lost all trust in code producing programs a long time ago since I used (and subsequently dumped) Microsoft Front Page to generate html. I am always wary of programs adding their own proprietary code to what I am writing. Front Page really messed up my html. If you are an Inkscape user, can you tell me if Inkscape places its own proprietary code in the svg that I would have to go into and strip out after svg creation?Prober
@Lady Aleena: Sort of. Regarding text wrapping, as mentioned in the FAQ I linked to, by default Inkscape has chosen to target the standard for text wrapping described in SVG 1.2 - so it IS using standards for this. I believe this decision was made some time ago, because at the time, it seemed like most implementations would move to support SVG 1.2 features. Unfortunately, this hasnt really won out. In any case, Inkscape does provide a way to make its text wrapping compatible with SVG 1.1 - its just not the default behaviour at this moment.Occasionally
The SVG generated by Inkscape can be quite verbose. By default, it saves SVG files with extra information that it uses to persist certain Inkscape-specific information. I guess, in a way, you could see this as proprietary, as it is Inkscape-specific, but its not proprietary in the same way that Frontpages use of HTML would be prorpietary, as: 1) this extra information is stored in a seperate XML namespace, and 2) you have the option to save as a Plain SVG file. Again, this simply pertains to default behaviour. The SVG generated by Inkscape is standards-compliant.Occasionally
Even the plain SVG generated by Inkscape can be a bit verbose, though, so for that you can use this tool: codedread.com/scourOccasionally
But, in general, Inkscape is a really great tool: open source and well-loved by the SVG community. If you`re interested in learning a tool to create web-friendly vector graphics, I think Inkscape is the first place you should look.Occasionally
I
33

The previous answers gave poor results when using rounded corners or stroke-width that's >1 . For example, you would expect the following code to produce a rounded rectangle, but the corners are clipped by the parent svg component:

<svg width="200" height="100">
  <!--this rect should have rounded corners-->
  <rect x="0" y="0" rx="5" ry="5" width="200" height="100" stroke="red" stroke-width="10px" fill="white"/>
  <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">CLIPPED BORDER</text>    
</svg>

Instead, I recommend wrapping the text in a svg and then nesting that new svg and the rect together inside a g element, as in the following example:

<!--the outer svg here-->
<svg width="400px" height="300px">

  <!--the rect/text group-->
  <g transform="translate(50,50)">
    <rect rx="5" ry="5" width="200" height="100" stroke="green" fill="none" stroke-width="10"/>
    <svg width="200px" height="100px">
      <text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">CORRECT BORDER</text>      
    </svg>
  </g>

  <!--rest of the image's code-->
</svg>

This fixes the clipping problem that occurs in the answers above. I also translated the rect/text group using the transform="translate(x,y)" attribute to demonstrate that this provides a more intuitive approach to positioning the rect/text on-screen.

Implement answered 1/7, 2017 at 4:16 Comment(1)
... and if you just want to center a text (without a rect), this works as well.Girasol
G
21

You can directly use text-anchor = "middle" property. I advise to create a wrapper svg element over your rectangle and text. That way you can use the whole element using one css selector. Make sure you place 'x' and 'y' property of text as 50%.

    <svg class="svg-rect" width="50" height="40">
        <rect x="0" y="0" rx="3" ry="3" width="50" height="40" fill="#e7e7e7"></rect>
        <text x="50%" y="50%" text-anchor="middle" stroke="black" stroke-width="1px" dy=".3em">N/A</text>
    </svg>
Greenleaf answered 11/8, 2016 at 21:10 Comment(0)
R
17

alignment-baseline is not the right attribute to use here. The correct answer is to use a combination of dominant-baseline="central" and text-anchor="middle":

<svg width="200" height="100">
    <g>
        <rect x="0" y="0" width="200" height="100" style="stroke:red; stroke-width:3px; fill:white;"/>
        <text x="50%" y="50%" style="dominant-baseline:central; text-anchor:middle; font-size:40px;">TEXT</text>
    </g>
</svg>
Rhodolite answered 14/11, 2018 at 9:44 Comment(1)
central worked for me were middle wasn't centerdClothilde
F
17

11 years too late for the party... use pathLength

x="50%" y ="50%" won't work in complex SVGs

No one mentioning pathLength

  • make it pathLength="2" here in a extra path id="#P"
  • then use the path as guide in the textPath href="#P":
  • so startoffset="1" is the middle of the blue path #P
  • and text-anchor="middle" aligns the text
  • (once all works, hide the blue line with stroke="transparent")
  • (or not use pathLength and set startoffset="50%")

<svg viewbox="0 0 200 50" style="background:pink">
  <rect x="10" y="10" width="180" height="30" stroke="green" fill="none"/>
  <path id="P" pathLength="2" d="M10 25h180" stroke="blue"/>
  <text>
    <textPath href="#P" 
              startoffset="1" text-anchor="middle" dominant-baseline="middle"
              fill="black" font-size="14px">aligned in middle</textPath>
  </text>
</svg>
Firebird answered 11/2, 2022 at 18:48 Comment(0)
C
9

Full detail blog: https://web.archive.org/web/20180717015233/http://blog.techhysahil.com:80/svg/how-to-center-text-in-svg-shapes/

<svg width="600" height="600">
  <!--   Circle -->
  <g transform="translate(50,40)">
    <circle cx="0" cy="0" r="35" stroke="#aaa" stroke-width="2" fill="#fff"></circle>
    <text x="0" y="0" alignment-baseline="middle" font-size="12" stroke-width="0" stroke="#000" text-anchor="middle">HueLink</text>
  </g>
  
  <!--   In Rectangle text position needs to be given half of width and height of rectangle respectively -->
  <!--   Rectangle -->
  <g transform="translate(150,20)">
    <rect width="150" height="40" stroke="#aaa" stroke-width="2" fill="#fff"></rect>
    <text x="75" y="20" alignment-baseline="middle" font-size="12" stroke-width="0" stroke="#000" text-anchor="middle">HueLink</text>
  </g>
  
  <!--   Rectangle -->
  <g transform="translate(120,140)">
    <ellipse cx="0" cy="0" rx="100" ry="50" stroke="#aaa" stroke-width="2" fill="#fff"></ellipse>
    <text x="0" y="0" alignment-baseline="middle" font-size="12" stroke-width="0" stroke="#000" text-anchor="middle">HueLink</text>
  </g>
  
  
</svg>
Countershaft answered 15/11, 2017 at 23:50 Comment(3)
Not centered in firefox while other solutions are. codepen.io/anon/pen/oEByYrAfteryears
I tested on Firefox 58.0.1 (64-bit), it's working. Can you mention the version for which it's not working it out for you?Countershaft
Firefox 58.0.2 64bit. The text is slightly higher than it should be. It might not be easily noticeable at first, but if your box fits very closely it's more obvious; try reducing the height.Afteryears
S
3

I had a bugger of a time getting anything centered using SVG, so I rolled my own little function. hopefully it should help you. Note that it only works for SVG elements.

function centerinparent(element) { //only works for SVG elements
    var bbox = element.getBBox();
    var parentwidth = element.parentNode.width.baseVal.value;
    var parentheight = element.parentNode.height.baseVal.value;
    var newwidth = ((parentwidth / 2) - (bbox.width / 2)) - 2; //i start everything off by 2 to account for line thickness
    var newheight = ((parentheight / 2) - (bbox.height / 2)) - 2;
    //need to adjust for line thickness??

    if (element.classList.contains("textclass")) { //text is origined from bottom left, whereas everything else origin is top left
        newheight += bbox.height; //move it down by its height
    }

    element.setAttributeNS(null, "transform", "translate(" + newwidth + "," + newheight + ")");
    // console.log("centering BOXES:  between width:"+element.parentNode.width.baseVal.value + "   height:"+parentheight);
    // console.log(bbox);
}
Subclavian answered 2/11, 2014 at 4:2 Comment(0)
B
2

One way to insert text inside a rectangle is to insert a foreign object, wich is a DIV, inside rect object.

This way, the text will respct the limits of the DIV.

var g = d3.select("svg");
					
g.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width","100%")
.attr("height","100%")
.attr("fill","#000");


var fo = g.append("foreignObject")
 .attr("width","100%");

fo.append("xhtml:div")
  .attr("style","width:80%;color:#FFF;margin-right: auto;margin-left: auto;margin-top:40px")
  .text("Mussum Ipsum, cacilds vidis litro abertis Mussum Ipsum, cacilds vidis litro abertis Mussum Ipsum, cacilds vidis litro abertis");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.js"></script>
<svg width="200" height="200"></svg>
Berseem answered 20/6, 2017 at 14:52 Comment(0)
G
2

For horizontal and vertical alignment of text in graphics, you might want to use the following CSS styles. In particular, note that dominant-baseline:middle is probably wrong, since this is (usually) half way between the top and the baseline, rather than half way between the top and the bottom. Also, some some sources (e.g. Mozilla) use dominant-baseline:hanging instead of dominant-baseline:text-before-edge. This is also probably wrong, since hanging is designed for Indic scripts. Of course, if you're using a mixture of Latin, Indic, ideographs or whatever, you'll probably need to read the documentation.

/* Horizontal alignment */
text.goesleft{text-anchor:end}
text.equalleftright{text-anchor:middle}
text.goesright{text-anchor:start}
/* Vertical alignment */
text.goesup{dominant-baseline:text-after-edge}
text.equalupdown{dominant-baseline:central}
text.goesdown{dominant-baseline:text-before-edge}
text.ruledpaper{dominant-baseline:alphabetic}

Edit: I've just noticed that Mozilla also uses dominant-baseline:baseline which is definitely wrong: it's not even a recognized value! I assume it's defaulting to the font default, which is alphabetic, so they got lucky.

More edit: Safari (11.1.2) understands text-before-edge but not text-after-edge. It also fails on ideographic. Great stuff, Apple. So you might be forced to use alphabetic and allow for descenders after all. Sorry.

Glaze answered 12/5, 2019 at 23:14 Comment(0)
W
0

Use !!! CSS better than text in react

enter image description here

<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg" style="border:1px solid blue;">

<!-- why react you can draw your border needed and padding with css free p and center text also flex -->
<switch>
    <foreignObject x="20" y="20" width="200" height="600" >

        
        <!-- text-align example css -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px dashed orange;display:flex;justify-content:center;">Aligned CSS free</p>


        <!-- flex example css 3 -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px dashed orange;display:flex;justify-content:center;">Aligned CSS free</p>
  
  
        <!-- flex example align end css 3 -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px dashed orange;display:flex;justify-content:flex-end;">Aligned CSS free</p>
   
   
       <!-- flex example align start css 3 -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px dashed orange;display:flex;align-items:center;justify-content:start;">Aligned CSS free</p>
        
  
  
         <!-- text justify -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px dashed orange;text-align:justify;">This is text-align justify for sentence</p>
        
       <!-- text-align center bigger react like  -->
        <p xmlns="http://www.w3.org/1999/xhtml" style="background:rgba(220,220,255,0.1);color:black;word-break:break-word;border:1px solid gray;padding:12px;text-align:center;">This is text-align justify for sentence</p>
        

        
      
      </foreignObject>
</switch>
</svg>
Windpipe answered 19/2 at 4:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.