Home > OS >  Stacked instances of SVG text with fixed widths and auto heights
Stacked instances of SVG text with fixed widths and auto heights

Time:08-17

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

1

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.

Capital height

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.

Uneven leading

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>
  • Related