Multiple layers of SVG text with consistent widths and dynamic heights

I have a challenge in mind where I want to implement a text effect that automatically adjusts its width while maintaining proportional heights.

My goal is to stack multiple words on top of each other to create a visual style similar to the example image below.

Example Image:

Do you think this type of effect can be achieved using SVG text or can it be done purely with CSS?

Answer №1

This is an example where I am utilizing your given text, but feel free to alter it as needed. The key concept here is using the textLength attribute to determine the length of the text.

The behavior of how the text is adjusted to fit the specified length is controlled by the lengthAdjust attribute.

Here, I have chosen to use

lengthAdjust="spacingAndGlyphs"
, however, you may opt for spacing instead.

Note the dx attribute in the top and bottom texts, which indicates a shift along the y-axis position of the tspan element. In this case, I have set it to 16 (as the font size).

Furthermore, keep in mind that the text is centered around the point x:0, y:0, but you have the flexibility to select a different point.

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>

Answer №2

It's highly likely that incorporating some JavaScript will be necessary to achieve the desired outcome.

The primary issue:
svg <text> elements do not support multi-line text or line heights. Therefore, you must divide your text content into multiple <tspan> elements with varying y offsets to simulate a similar structure to an HTML <p>.

Additionally, numerous critical properties cannot currently be styled using CSS. This includes x and y, which are essential for replicating a line height.

Example: simulating multi-line SVG text - adjusting font size based on 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 class="svgPseudoP" 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>

These steps are essential for achieving the desired result:

  • Set a specific line width that all lines should adhere to (e.g., the first line/text element).
  • Determine each line's width using text.getComputedTextLength().
  • Adjust the font size accordingly:
    let scale = widthIdeal / widthCurrentLine;

    let newFontSize = fontSizeFirst * scale
  • Calculate the line height/leading:
    This involves finding the ratio between the height of capital letters and the font's em square – without this, lines with larger fonts may have significantly larger margins than smaller ones.

https://i.sstatic.net/mCO6p.png

For instance, create a capital letter in Arial at 100 points using software like Inkscape or Illustrator, convert it to paths/outlines, and check its height: 71.582 pt. Consequently, the capital to em square ratio would be: 100/71.582 = 0.71582

This value varies based on the metrics of the actual font files, meaning there is no standardized capital letter height. However, a ratio around 0.72–0.75 typically works well for many font families.

https://i.sstatic.net/XineV.png

Example: Irregular leading due to a non-ideal capital to em square ratio.

The provided example code will also divide markup based on new lines into <tspan> elements:

<text x="50%" y="20" font-size="10">
    Example
    Text
    Goes here
</text>   

will be transformed into:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating elements in Polymer 2.0 is a breeze. Simply use the `createElement` method, then seamlessly import it using `Polymer

While working on a project, I encountered an issue where I was creating and importing an element while setting attributes with data. The code should only execute if the element hasn't been imported or created previously. However, each time I called th ...

Arranging divs precisely while rotating a Bootstrap Thumbnail

I've been experimenting with creating a "css3 on hover flip" effect for bootstrap thumbnails. It works perfectly fine on simple divs (check it out here) Here's the main CSS3 code I'm using: .front{ position:absolute; transform:pers ...

How can I verify if a user is logged in using express.Router middleware?

Is there a way to incorporate the isLoggedIn function as a condition in a get request using router.route? const controller = require('./controller'); const Router = require('express').Router; const router = new Router(); function isLo ...

Changing the form upon submission - Utilizing jQuery for Ajax requests

I am currently working on a form to manage items in favorites, allowing users to add and delete them. Below is the code for the form: @if($checkIfFavIsAdded == 0) {!! Form::open(['id' => 'ajax-form-add', 'style' => ...

The range input in HTML does not respond when attempting to adjust it

I am having an issue where the thumb on the slider does not move when I try to. This problem occurred after I inserted an img element inside the slider-cntnr div. View the code at http://codepen.io/danielyaa5/pen/xVEXvB HTML <div id="slider-cntnr"&g ...

Protractor unable to locate elements using by.repeater

What is the best method for targeting this repeater with Protractor? <a ng-repeat="item in filteredItems = (items | filter:'abc')">{{item}}</a> ...

Navigating through elements in the hidden shadow DOM

Can elements within the Shadow DOM be accessed using python-selenium? For example: There is an input field with type="date": <input type="date" name="bday"> I want to click on the date picker button located on the right and select a ...

How can I customize the styling of Autocomplete chips in MUI ReactJS?

Trying to customize the color of the MUI Autocomplete component based on specific conditions, but struggling to find a solution. Any ideas? https://i.stack.imgur.com/50Ppk.png ...

Instructions on activating dark mode with the darkreader plugin on a Vue.js website

Is there a way to implement DarkMode in a Vue.js application? I attempted to integrate darkmode using this npm package, but I kept encountering the error message: DarkMode not defined. ...

What are the methods for altering the material of a glTF model using THREE.js?

I've created a model using Blender and baked all the lighting and textures. However, when I import it into THREE.js in .glb format, it automatically uses the standard material. While this may work in certain cases, my concern is that I want to retain ...

Angular throws an error when attempting to access a property that is undefined

I created the following angular template: <section class="section"> <div class="container"> <form [formGroup]="testForm"> <div class="columns is-multiline"> <div class="column is-2"> ...

Adjusting the transparency level of the JavaScript thumbnail

I have implemented a plugin to showcase an image carousel on my website. The navigation thumbnails in the carousel are currently set to appear "greyed out" when not selected. Is there a way to adjust the level of grey for these thumbnails? Check out the l ...

Java update query experiencing issues

This servlet is designed to add a new user entry into a database table. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String firstname = request.getParameter("firstname"); Str ...

Combine similar functions in jQuery and JavaScript when using document.ready and ajax load

Utilizing the ajaxify.js plugin from https://github.com/browserstate/ajaxify for dynamic content loading. I've encountered an issue with binding multiple click functions in a document ready state and having to re-bind them within the ajax load functi ...

Restart animation following a scroll occurrence

My experiment involves using the anime.js framework to create a simulation of leaves falling from a tree. The animation triggers when the user scrolls to the top of the page. However, I am facing an issue where the animation only plays once; if the user sc ...

Having trouble setting a value as a variable? It seems like the selection process is not functioning properly

My Hangman game has different topics such as cities and animals. When a user selects a topic, the outcome should be a random item from that specific topic. For example: London for cities or Zebra for animals. Currently, I am only generating a random lett ...

Rotation to a point in a circle using three.js

Currently, I am in possession of 2 instances of THREE.Vector3(). My task at hand is to create a circular shape around one vector with the second vector serving as a tangent. I have determined the radius of the circle geometry based on the distance betwee ...

Error Alert: UnhandledPromiseRejectionWarning in Async Series Causes Concern

I am currently working on a function in my project that uses the async series method to ensure specific tasks are executed in a certain order. In this case, function1 needs to be completed before function2, which must then be completed before function3. a ...

Leveraging 2-dimensional indexes in collaboration with the $geoNear operator

I encountered an issue while attempting to use geoNear with aggregate as I received the following error message: errmsg: "'near' field must be point" The reason for this error is because my location field is represented as [Number]: var locati ...

Tips for maintaining an updated array's consistency on page refresh in Vue.js?

HelloWorld.vue Dynamic routing in Vuejs: <template> <div> <b>Vuejs dynamic routing</b> <div v-for="item in items" :key="item.id"> <b>{{ item.id }}.</b> &nbsp;&nbsp;&nbsp; <rou ...