I'm looking to create a text effect in which lines of text automatically scale to hit a specifically defined width, with auto-adjusting heights.
Ideally, I would then be able to stack multiple words on top of one another to achieve something visually similar to the below.
Example image
Is this something that should be possible with SVG text? Could it be done through pure CSS?
CodePudding user response:
In the next example I'm using yourtext but you can change it. The main idea is using textLength to set thelength of the text.
The lengthAdjust attribute controls how the text is stretched into the length defined by the textLength attribute.
In this case I'm using lengthAdjust="spacingAndGlyphs"
but you may want to use spacing
instead.
Please observe that the top and bottom text have a dx attribute that indicates a shift along the y-axis on the position of the tspan element. In thise case I'm choosing 16 (as the sont size)
also please observe that the text is centered around the point x:0,y:0. You can choose a different one.
svg {
font-family:arial;
font-weight:bold;
font-size:16px;
width: 90vh;
border: solid;
}
<svg viewBox="-50 -50 100 100">
<text text-anchor="middle" dominant-baseline="middle">
<tspan dy="-16" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="top">EXAMPLE</tspan>
<tspan y="0" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="mid">TEXT</tspan>
<tspan dy="16" x="0" textLength="70" lengthAdjust="spacingAndGlyphs" id="bottom">GOES HERE</tspan>
</text>
</svg>
CodePudding user response:
Quite likely, you should include some javaScript to get the desired result.
The main problem:
svg <text>
elements don't have anything like multi line text or line heights. So you need to split your text content into a lot of <tspan>
elements with different y
offsets to emulate something similar to a HTML <p>
.
Besides, a lot of important properties can't yet be stylesd with css. Most importantly x
and y
which are crucial to mimic a line height.
Example: mimic multi line svg text - scale font-size to width
let svg = document.querySelector('svg')
let svgPseudoP = document.querySelector('.svgPseudoP');
svgSplitTextLines(svgPseudoP)
//split newlines
function svgSplitTextLines(el) {
let texts = el.querySelectorAll('text');
for (let t = 0; t < texts.length; t ) {
let text0 = texts[t];
let [x0, y0] = [text0.getAttribute('x'), text0.getAttribute('y')];
//trim empty elements and whitespace
let lines = text0.innerHTML.split(/\r?\n/);
lines = lines.map((str) => {
return str.trim()
}).filter(Boolean)
//set first line as textContent
text0.textContent = lines[0];
// calculate proportions
let width0 = text0.getComputedTextLength();
let style0 = window.getComputedStyle(text0);
let fontSize0 = parseFloat(style0.getPropertyValue('font-size'));
// ratio between capital letter height and font size
let ascenderRatio = 0.71582;
// define ideal leading
let leading = fontSize0 * 0.2;
for (let i = 1; i < lines.length; i ) {
let str = lines[i];
let tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.textContent = str;
tspan.setAttribute('x', x0);
text0.appendChild(tspan);
// scale font size according to width
let width = tspan.getComputedTextLength();
let scale = width0 / width;
let newFontSize = parseFloat(fontSize0) * scale;
tspan.setAttribute('style', 'font-size:' newFontSize 'px');
// emulate line height by increasing Y offset
let tspanPrev = tspan.previousElementSibling;
let yPrev = tspanPrev ? tspanPrev.getAttribute('y') : text0.getAttribute('y');
let newY = yPrev (newFontSize * ascenderRatio)
tspan.setAttribute('y', newY leading);
}
}
}
svg {
width: 50%;
border: 1px solid #ccc;
}
text {
font-family: Arial;
font-weight: bold;
text-anchor: middle;
text-transform: uppercase;
}
<svg viewBox="0 0 100 100">
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
<text x="25%" y="60" font-size="8">
Example2
Text
Goes
here
</text>
</svg>
You will essentially need these steps:
- set a desired line width that all lines should get (this could e.g be the first line/text element)
- get each line's width via
text.getComputedTextLength()
- scale the font-size accordingly:
let scale = widthIdeal / widthCurrentLine;
let newFontSize = fontSizeFirst * scale
- calculate line height/leading
This will require to get the ratio between capital letter height and the fonts em square – otherwise lines with larger font sizes will add larger margins than smaller ones.
E.g write a capital in Arial at 100 points in inkscape, Illustrator etc. and convert it to paths/outlines and check it's height: 71.582 pt
So the capital to em square ratio is: 100/71.582 = 0.71582
This value depends on the actual font files metrics – so there is nor standardized capital letter height. However a ratio about 0.72–0.75 should be fine for a lot of font families.
Example: Uneven leading due to not ideal capital to em square ratio.
The above example code will also split markup based new lines to <tspan>
elements:
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
will be converted to:
<text x="50%" y="20" font-size="10">
Example
<tspan x="50%" style="font-size:18.9px" y="35.5">Text</tspan>
<tspan x="50%" style="font-size:8.1px" y="43.3">Goes here</tspan>
</text>