I 'm working on rotating the text in an SVG text element. Whene a rotation is performed using transform:rotate(angle,x,y) entire text element is rotated. This creates an issue that positioning of the text element will be modified.
I understand this behavior, because rotaion causes tanslation of co-ordinates. But what I want to do is, the 'x' and 'y' position should always be the bottom of text element.
For example, the following code will create a text element at 50, 50
<text x='50' y='50'>My Text </text>
Now, (left, bottom) of text is (50, 50). If I rotate it by 90, then (left, top) will be 50, 50.
So, for all kind of rotations, I don't want to change the position of text element. How can I do this?
You can anchor the text in different positions using text-anchor. For example, if you rotate the text like this:
<text id="mytext" x='50' y='50'>My Text
<animateTransform attributeName="transform" begin="0s" dur="5s" type="rotate"
from="0 50 50" to="360 50 50" repeatCount="indefinite"/>
</text>
It will use the upper-left corner as an anchor-point. You can move it to the center using:
<text id="mytext" x='50' y='50' text-anchor="middle">My Text
<animateTransform attributeName="transform" begin="0s" dur="5s" type="rotate"
from="0 50 50" to="360 50 50" repeatCount="indefinite"/>
</text>
And now it will rotate anchored in the center: see this JSFiddle.
You might also want to experiment with other anchor and baseline properties, if this is not exactly what you want. See: SVG Spec - 10 Text - 10.9 Alignment properties
EDIT
Sorry for my original answer below...I had not yet had my first cup of coffee;)
Try adding text-anchor="end" and dy="1"
IGNORE BELOW
Try the following:
Text rotate attribute is an object that specifies or receives a list of individual character rotation values. The last rotation value rotates all following characters.
myText.setAttribute("rotate","deg1, deg2, deg3, deg4, ...")
rotationNumbs=myText.getAttribute("rotate")
~ or ~
[ NumberList = ] myText.rotate [ = NumberList ]
Related
I have some code in a project where I try to center a text element inside of a rect element based on the rect height + widht as well as the width and height of the text. It works fine horizontally but applying the same logic vertically fails.
Aligning the text horizontally is easy. I simply get the bounding box of the text to calculate how wide it is, then I subtract that width from the total width of the box to find how much space will be left inside the rectangle. Then I divide that width in half to know how much space I need to padd my x-coordinate of my text in order for it to be equal on both sides:
My code for getting information of the rectangle:
let rect = document.getElementById("rectangle");
let rectXCoordinate = Number(rect.getAttribute("x"));
let rectYCoordinate = Number(rect.getAttribute("y"));
let rectWidth = Number(rect.getAttribute("width"));
let rectHeight = Number(rect.getAttribute("height"));
My code for centering the text horizontally:
let text = document.getElementById("text");
let textBBox = text.getBBox();
let textWidth = textBBox.width;
let horizontalPadding = (rectWidth - textWidth) / 2;
let textX = rectXCoordinate + horizontalPadding;
text.setAttribute("x", textX);
However, the issue is when I try to apply similar logic vertically. The way I understand it, a rectangle is drawn with its 0,0 point at the top left corner. Which means positive y equals down, and positive x equals right.
Text seems to be drawn with 0,0 at the bottom of the text, meaning that positive y is up and positive x is right.
So in order to compensate for this I first calculate the bottom coordinate of the rectangle, by adding the rectangle y with the rectangle height.
Then I subtract the vertical padding using the same logic as in the horizontal example. I subtract the text height from the total height of the rectangle and divide it by 2.
See code below:
let textHeight = textBBox.height;
let verticalPadding = (rectHeight - textHeight) / 2;
let textY = rectYCoordinate + rectHeight - verticalPadding;
text.setAttribute("y", textY);
The problem is, the text is slightly off center and I cannot for the life of me understand why.
Is there some fundamental that I am misunderstanding in regards to how text is drawn?
I understand that there are other ways to do this, and I have solved it differently in my project, so please refrain from giving examples on alternative ways to solve this. I am just curious to know why this logic fails, I would like to understand what is going on.
Here is a codepen live example with the same code as I've inserted above:
https://codepen.io/Sorry-not-sorry/pen/QWBzwjW
Adding a more detailed explanation here for future readers:
The baseline affects the position of the text within the text bounding box. Note that the default for the alignment-baseline is baseline. Run the snippet below and you'll notice that the extender for letter 'y' goes below the line and the ascender on the letter 'b' doesn't reach to the top of the line above it when the baseline is set to the default.
What's happening in your JavaScript is that the getBBox().height is returning the height of the text bounding box, but not the placement of the text within it. Therefore, if you change the value of the baseline to ideographic or text-after-edge, the text will be placed fully within the bounding box.
<div style="display: flex;">
<div>
<h2>Dominant-baseline</h2>
<svg viewBox="0 0 180 120" xmlns="http://www.w3.org/2000/svg" height="200">
<path d="M0,15 L150,15 M0,30 L150,30 M0,45 L150,45 M0,60 L150,60 M0,75 L150,75 M0,90 L150,90" stroke="#ccc" />
<text dominant-baseline="ideographic" x="10" y="30">ideographic</text>
<text dominant-baseline="baseline" x="10" y="45">baseline y</text>
<text dominant-baseline="middle" x="10" y="60">middle</text>
<text dominant-baseline="hanging" x="10" y="75">hanging</text>
<text dominant-baseline="text-before-edge" x="10" y="90">text-before-edge</text>
<text dominant-baseline="text-after-edge" x="10" y="15">text-after-edge</text>
</svg>
</div>
<div>
<h2>Alignment-baseline</h2>
<svg viewBox="0 0 180 120" xmlns="http://www.w3.org/2000/svg" height="200">
<path d="M0,15 L150,15 M0,30 L150,30 M0,45 L150,45 M0,60 L150,60 M0,75 L150,75 M0,90 L150,90" stroke="#ccc" />
<text alignment-baseline="ideographic" x="10" y="30">ideographic</text>
<text alignment-baseline="baseline" x="10" y="45">baseline</text>
<text alignment-baseline="middle" x="10" y="60">middle</text>
<text alignment-baseline="hanging" x="10" y="75">hanging</text>
<text alignment-baseline="text-before-edge" x="10" y="90">text-before-edge</text>
<text alignment-baseline="text-after-edge" x="10" y="15">text-after-edge</text>
</svg>
</div>
</div>
Warning: The problem gets more complicated when you're dealing with SVGs in HTML because they can be affected by CSS. The default font-size of the text element can be overridden by setting the font-size on the HTML body if the text element font-size isn't being explicitly set on the text element using the font-size attribute. Even then, you can have CSS that overrides this such as text { font-size: 18px } in a stylesheet. The getBBox() method doesn't know anything about the font-size applied via CSS in order to calculate the height correctly.
How to center the text reliably
This approach will reliably set the text in the center of the rect regardless of any CSS affecting the font-size:
Group the text and rect together if you want to be able to move them from the 0,0 point. This way you don't have to account for the position of the rect relative from the SVG origin.
Set the rect height and width.
Set the text x position to 1/2 of the width of the rect.
Set the text y position to 1/2 of the height of the rect.
Set the text-anchor property to middle on the text element.
Set the baseline-alignment to central on the text element to center the text vertically. The central value matches the box's central baseline to the central baseline of its parent.
Set the text-anchor property on the text element to middle to center the text horizontally.
Use a transform to move the group into the final desired position within the viewBox.
Demo
<svg id="viewbox" viewBox="0 0 100 100" height="200">
<g transform="translate(25,25)">
<rect width="30" height="30" fill="white" stroke="currentColor" />
<text x="15" y="15" alignment-baseline="central" text-anchor="middle"> Hi </text>
</g>
</svg>
And, of course, this would be even easier to reproduce with JavaScript than your proposed approach, because you only need the rect x and y attributes to calculate the x and y position of the text element.
This question already has answers here:
SVG Line with Gradient Stroke Won't Display if vertical or horizontal
(3 answers)
How to add a linearGradient to a vertical SVG line [duplicate]
(2 answers)
SVG Mask makes line disappear
(1 answer)
Closed 7 months ago.
Update: Gets weirder. If the line is horizontal it disappears, but if it has any slope at all, it shows up just fine. Look at id="horizontalNoShow" if you change it so y1 and y2 are not equal, it will render.
I think this is a bug but not sure. Happens in Chrome and Safari. Trying to add an SVG linearGradient to a line.
I can add it to all other shapes, but when I add it to the line, the line disappears. Still shows up in the DOM, but just not getting rendered for some reason?
I have a purple line that shows up great. I have a rectangle with a gradient stroke that shows up great. But when I combine the gradient stroke with the line, it doesn't show up.
<svg width="" height="">
<defs>
<linearGradient id="FirstGradient" >
<stop offset="0%" style="stop-color:#FF00FF"/>
<stop offset="100%" style="stop-color:#FFFF00"/>
</linearGradient>
</defs>
<line id="someSlopeShow" x1="50" y1="70" x2="250" y2="71"
stroke="url('#FirstGradient')"
stroke-width="6"
/>
<line id="horizontalNoShow" x1="55" y1="90" x2="255" y2="90"
stroke="url('#FirstGradient')"
stroke-width="6"
/>
<rect id="exampleTwoRectSVG"
x="10" y="10"
width="200" height="100"
stroke="url(#FirstGradient)"
stroke-width="15"
fill='transparent'
stroke-dasharray="110 20"
/>
</svg>
Found the answer. The spec for linearGradient show that is uses an object bounding box. https://www.w3.org/TR/SVG/coords.html#ObjectBoundingBoxUnits
However in the last paragraph:
"Keyword objectBoundingBox should not be used when the geometry of the
applicable element has no width or no height, such as the case of a
horizontal or vertical line, even when the line has actual thickness
when viewed due to having a non-zero stroke width since stroke width
is ignored for bounding box calculations. When the geometry of the
applicable element has no width or height and objectBoundingBox is
specified, then the given effect (e.g., a gradient or a filter) will
be ignored."
This makes sense since the box needs an area to work. And a horizontal line doesn’t have any area.
A hacky fix is you can fix it by adding .001 to your coordinates to give it a tiny bit of area.
<svg width="" height="">
<defs>
<linearGradient id="FirstGradient" >
<stop offset="0%" style="stop-color:#FF00FF"/>
<stop offset="100%" style="stop-color:#FFFF00"/>
</linearGradient>
</defs>
<line id="horizontalNoShow" x1="55" y1="90" x2="255" y2="90.001"
stroke="url('#FirstGradient')"
stroke-width="6"
/>
</svg>
If I understand this correctly it means that none of the following features:
linearGradient
radialGradient
pattern
clipPath
mask
filter
will work if your line is horizontal or vertical.
I had a hard time finding the answer on StackOverFlow because I didn't realize it was the horizontal or vertical line that caused the issue.
These questions cover those well: How to add a linearGradient to a vertical SVG line
SVG Line with Gradient Stroke Won't Display if vertical or horizontal (this question name isn't great. Straight couldn't mean a lot of things.)
SVG Masks also don't work on vertical or horizontal lines: SVG Mask makes line disappear
feDropshadows also don't work on vertical or horizontal lines: Adding feDropShadow to a vertical line in SVG makes it disappear
I have an app that allow user to draw SVG multiline texts. I checked multiple solutions on StackOverflow and tspan seems to be the best, however I have the issue when the parent text is not positioned at the left (i.e x is not 0):
<svg viewBox="0 0 500 500">
<text x="100" y="100">
<tspan>A</tspan>
<tspan dx="0" dy="1.2em">B</tspan>
<tspan x="0" dy="1.2em">C</tspan>
</text>
</svg>
As you can see, the B line doesn't move back at all and the C line is moved to the left of the whole canvas instead of at text's 100px. Is there anyway other than setting each tspan manually?
I am working on a svg graphic that will represent health bar in a game, thus far it is looking like this https://jsfiddle.net/8ds9hpuv
Concept is to have responsive bar that decreases / increases in width based on character health.
Right now I can't figure out how to decrease this path in width, but maintain that rounded edge on the right side all the time.
Ideally I would like to make it's height responsive as well
<svg width="428" height="35">
<path d="M0 0h414.333785C423.444595 9.346449 428 15.179782 428 17.5c0 2.320218-4.555405 8.153551-13.666215 17.5H0V0z" fill="red"/>
</svg>
I've modified the path by changing every command to lowercase (using this tool: Convert SVG path to all-relative or all-absolute ) but I've left the last H command to uppercase since H0 is going back to x="0"
Next I'm replacing the first h command with the variable healthIndicator
I'm assuming that the tip of the arrow has only an aesthetical function.
For the sake of the demp I'm using an input type range to change the value of the healthIndicator. I hope this is what you need.
itr.addEventListener("input",()=>{
let healthIndicator = itr.value;
let d = `M0,0 h${healthIndicator}c9.111,9.346,13.666,15.18,13.666,17.5c0,2.320218,-4.555405,8.153551,-13.666215,17.5H0v-35z`;
thePath.setAttributeNS(null,"d", d);
})
svg{border:1px solid}
<svg viewBox="0 0 550 35" >
<path id="thePath" d="M0,0
h414
c9.111,9.346,13.666,15.18,13.666,17.5
c0,2.320218,-4.555405,8.153551,-13.666215,17.5
H0z" fill="red"/>
</svg>
<input type="range" id="itr" value="414" min="0" max="500" />
I've got an SVG of an iPhone, on it I've added some JS to change the clocks time and date to the current date/time relative to the user.
The image is an SVG and inside I'm defining the text elements with a <text> tag but it seems that I can only y-align or x-align each tag. The issue with this is as the numbers change, some of the numbers have different widths which then pushes it either closer or further away from its sibling.
Basically, I'm wanting to achieve something which can be done with flex. For example, if .flex has flexbox assigned to it:
<div class="flex">
<div>13</div>
<div>:</div>
<div>23</div>
</div>
This would pull all the divs inline and the widths of the divs would expand where necessary keeping everything inline, neat and tidy. Like this.
I'm wanting the widths of the elements inside the SVG to change, but stay centered in the image without offsetting to the left or right a little but. The same applies for the date also.
Please see this example: https://codepen.io/mrmathewc/pen/vodbmm
The screen the time and date are contained in is 300px in width.
Search the id hours, minutes, date within the SVG to see where the <text> element is positioned.
I've seen someone mention putting <rect> elements above the text items, which I've tried but not had any luck achieving what I want.
I've also tried adding the time into one <text> element, rather than splitting it up but again, as the numbers change my issue persists.
If anyone could shine some light on this, I'd really appreciate it!
Thanks
A solution would be to use text-anchor so that you can align hours to the right and the full date to the middle. It will adapt to the text size:
<g id="Group" opacity="0.8" transform="translate(285.777635, 160.997429)" fill="#FFFFFF" fill-rule="nonzero">
<text text-anchor="end" x="60" y="0" fill="white" class="clock-numerals" id="hours">00</text>
<text x="60" y="0" fill="white" class="clock-numerals">:</text>
<text x="75" y="0" fill="white" class="clock-numerals" id="minutes">00</text>
</g>
<g id="Group" opacity="0.8" transform="translate(294.228792, 190.920308)" fill="#FFFFFF" fill-rule="nonzero">
<text text-anchor="middle" x="60" fill="white" class="clock-date" id="date">1</text>
You can try <tspan and you can give it x and y like this:
<svg viewBox="0 0 240 40" xmlns="http://www.w3.org/2000/svg" width="500">
<text x="10" y="30" class="small">
You are
<tspan x="10" y="40">not</tspan>
<tspan x="10" y="50">a banana!</tspan>
</text>
</svg>
which gives you this:
enter image description here
notice that tspan and text have the same x and tspan y increases so it will appear lower.
You can learn more here: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan