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.)
An easy solution to center text horizontally and vertically in SVG:
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). 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. 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:
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;
}
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>
dominant-baseline
and text-anchor
, not dominantBaseline
and textAnchor
?
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.
editable="true"
to the text or textArea element.
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:
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:
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.
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%.
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"
:
central
worked for me were middle
wasn't centerd
Full detail blog: https://web.archive.org/web/20180717015233/http://blog.techhysahil.com:80/svg/how-to-center-text-in-svg-shapes/
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);
}
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 2 here, so 1 is the half) and textPath
:
https://i.imgur.com/8wuf7r7.png
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");
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.
Success story sharing
dominant-baseline
property that does the job, not thealignment-baseline
.<style type="text/css"><![CDATA[ text { alignment-baseline: middle; text-anchor:middle; } ]]></style>
. Thanks for the solution.alignment-baseline
, but it seems that it may be a quirky thing because reviewing the specs, it should bedominant-baseline
fortext
, andalignment-baseline
fortspan
,tref
,altGlyph
, andtextPath
. I updated the answer accordingly.dominant-baseline="central"
gave the look I wanted, FWIW