Ways to center text vertically using CSS

It appears that certain letters like g, y, q, etc. which have a downward sloping tail, are causing issues with vertical centering. Here is an image illustrating the problem https://i.sstatic.net/Pcnl8.png.

The characters inside the green box are perfectly aligned, as they lack a downward tail. The ones in the red box highlight the issue.

I am seeking to achieve perfect vertical centering for all characters. In the provided image, characters with a downward tail are not vertically centered. Is there a way to solve this?

Here is the fiddle showcasing the complete problem.

.avatar {
    border-radius: 50%;
    display: inline-block;
    text-align: center;
    width: 125px;
    height: 125px;
    font-size: 60px;
    background-color: rgb(81, 75, 93);
    font-family: "Segoe UI";
    margin-bottom: 10px;
}

.character {
    position: relative;
    top: 50%;
    transform: translateY(-50%);
    line-height: 100%;
    color: #fff;
}
<div class="avatar">
  <div class="character">W</div>
</div>

<div class="avatar">
  <div class="character">y</div>
</div>

Answer №1

My approach involves utilizing JavaScript to convert the element into an image, extracting pixel data, and then iterating through them to identify the top and bottom of each character. By applying a translation to align these elements properly, this method accommodates dynamic font properties.

Although the code below is not fully optimized, it effectively demonstrates the core concept:

var elems = document.querySelectorAll(".avatar");
var fixes = [];

for (var i = 0; i < elems.length; i++) {
  var current = elems[i];
  domtoimage.toPixelData(current)
    .then(function(im) {
      /* Find the top limit */
      var t = 0;
      for (var y = 0; y < current.scrollHeight; ++y) {
        for (var x = 0; x < current.scrollWidth; ++x) {
          var j = (4 * y * current.scrollHeight) + (4 * x);
          if (im[j] == 255 && im[j + 1] == 255 && im[j + 2] == 255) {
            t = y;
            break;
          }
        }
      }
      /* Find the bottom limit*/
      var b = 0;
      for (var y = (current.scrollHeight - 1); y >= 0; --y) {
        for (var x = (current.scrollWidth - 1); x >= 0; --x) {
          var j = (4 * y * current.scrollHeight) + (4 * x);
          if (im[j] == 255 && im[j + 1] == 255 && im[j + 2] == 255) {
            b = current.scrollHeight - y;
            break;
          }
        }
      }
      /* Calculate the difference and apply a translation*/
      var diff = (b - t)/2;
      fixes.push(diff);
      /* Apply the translation once all are calculated*/
      if(fixes.length == elems.length) {
        for (var k = 0; k < elems.length; k++) {
          elems[k].querySelector('.character').style.transform = "translateY(" + fixes[k] + "px)";
        }
      }
    });
}
.avatar {
  border-radius: 50%;
  display: inline-flex;
  vertical-align:top;
  justify-content: center;
  align-items: center;
  width: 125px;
  height: 125px;
  font-size: 60px;
  background: 
    linear-gradient(red,red) center/100% 1px no-repeat,
    rgb(81, 75, 93);
  font-family: "Segoe UI";
  margin-bottom: 10px;
}

.character {
  color: #fff;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<div class="avatar">
  <div class="character">W</div>
</div>

<div class="avatar">
  <div class="character">y</div>
</div>

<div class="avatar">
  <div class="character" style="font-size:35px">a</div>
</div>

<div class="avatar">
  <div class="character" style="font-size:25px">2</div>
</div>
...

UPDATE

Below is an optimized version of the code:

var elems = document.querySelectorAll(".avatar");
var k = 0;

for (var i = 0; i < elems.length; i++) {
  domtoimage.toPixelData(elems[i])
    .then(function(im) {
     var l = im.length;
      /* Find the top limit */
      var t = 0;
      for (var j = 0; j < l; j+=4) {
          if (im[j+1] == 255) { /* We only need to check the G component since we know the colors */
            t = Math.ceil((j/4)/125);
            break;
          }
      }
      /* Find the bottom limit*/
      var b = 0;
      for (var j = l - 1; j >= 0; j-=4) {
          if (im[j+1] == 255) {
            b = 125 - Math.ceil((j/4)/125);
            break;
          }
      }
      /* Calculate the difference and apply a translation*/
      elems[k].querySelector('.character').style.transform = "translateY(" + (b - t)/2 + "px)";
      k++;
    });
}
.avatar {
  border-radius: 50%;
  display: inline-flex;
  vertical-align:top;
  justify-content: center;
  align-items: center;
  width: 125px;
  height: 125px;
  font-size: 60px;
  background: 
    linear-gradient(red,red) center/100% 1px no-repeat,
    rgb(81, 75, 93);
  font-family: "Segoe UI";
  margin-bottom: 10px;
}

.character {
  color: #fff;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<div class="avatar">
  <div class="character">W</div>
</div>

<div class="avatar">
  <div class="character">y</div>
</div>

<div class="avatar">
  <div class="character" style="font-size:35px">a</div>
</div>

<div class="avatar">
  <div class="character" style="font-size:25px">2</div>
</div>
...

I am using dom-to-image plugin for this.

Answer №2

Perhaps there is a more efficient solution, but it seems like the only approach is to manually apply different styles based on whether the character is a:

  • Capital letter
  • Lowercase with a tail
  • Lowercase with a stalk
  • Lowercase with neither

It's worth noting that the proportions of tails and stalks are typically defined by the font itself. It may require adjusting these values in accordance with the chosen font programmatically.

It should also be mentioned that this method wouldn't easily support multiple languages, as every character would need to be categorized across numerous character sets.

const letters = ['a', 'b', 'y', 'X', 'c', 'y', 'A', 'B', 'Y']; 

function getAdditionalClass(char){
    //To do - fill arrays with the rest of the appropriate letters
    if (['y', 'g'].includes(char)) {
        return "tail"; 
    }
    if (['b', 'd'].includes(char)) {
        return "stalk"; 
    }
    
    if (['a', 'c'].includes(char)) {
        return "small"; 
    }
    
    return "capital"; 
}

letters.forEach(v => {
  const avatar = document.createElement("div"); 
  avatar.className = "avatar"; 
  const character = document.createElement("div");
  character.textContent = v; 
  character.className = `character ${getAdditionalClass(v)}`; 
  
  avatar.appendChild(character); 
  
  const root = document.getElementById("root"); 
  
  root.appendChild(avatar); 
  
});
.avatar {
    border-radius: 50%;
    display: block;
    text-align: center;
    width: 125px;
    height: 125px;
    font-size: 60px;
    background-color: rgb(81, 75, 93);
    font-family: "Segoe UI";
    margin-bottom: 10px;
}

.character {
    position: relative;
    transform: translateY(-50%);
    line-height: 100%;
    color: #fff;
}


.small {
    top: 45%; 
}

.stalk {
    top: 50%;
}

.tail {
    top: 41%;
}

.capital {
    top: 50%;
}

#root {
    display: flex; 
    flex-flow: row wrap; 
}
<div id = "root">

</div>

Answer №3

This situation is quite tricky!

It seems that achieving native scalability might be a challenge in this case (using %, vw or vh values instead of px or em). If you want it to look good on mobile or tablet devices, consider implementing my solution with @media breakpoints.

My approach involves identifying lowercase elements with tails and applying a class to adjust the height accordingly. Based on my tests, it seemed that no extra handlers were needed for uppercase letters or lowercase letters without tails. Please correct me if I'm mistaken.

Feel free to experiment with and modify this solution by checking out the JSFiddle provided here.

var circles = document.getElementsByClassName('circle');
var tails = ['q', 'y', 'p', 'g', 'j'] ;

Array.from(circles).forEach(render);
  
function render(element) { 
    if(element.innerText == element.innerText.toLowerCase() &&
    tails.includes(element.innerText)) {
    element.className += " scale";
    }
}
.circle {
  height: 150px;
  width: 150px;
  background-color: #bbb;
  border-radius: 50%;
  display: inline-block;
  text-align: center;
  vertical-align: middle;
  line-height: 150px;
  font-size: 50px;
}

.scale {
  line-height: 135px;
}
<div>
  <div class="circle">W</div>
  <div class="circle">X</div>
</div>
<div>
  <div class="circle">y</div>
  <div class="circle">t</div>
</div>

Share your feedback and let me know if I overlooked anything. Collaborating on finding a final solution would be great, especially since I've faced similar challenges in the past!

Answer №4

To efficiently handle the translation of lowercase and capital letters, creating a helper class would be beneficial. A straightforward script can be implemented to automatically assign these helper classes.

I trust this solution addresses your issue :)

.avatar {
    border-radius: 50%;
    display: block;
    text-align: center;
    width: 125px;
    height: 125px;
    font-size: 60px;
    background-color: rgb(81, 75, 93);
    font-family: "Segoe UI";
    margin-bottom: 10px;
}

.character {
    position: relative;
    top: 50%;
    line-height: 100%;
    color: #fff;
}
.character-lowercase {
  transform: translateY(-60%);
}
.character-capital {
  transform: translateY(-50%);
}
<div class="avatar">
  <div class="character character-capital">W</div>
</div>

<div class="avatar">
  <div class="character character-lowercase">y</div>
</div>

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

Retrieve the value of a tag attribute while the tab is in the active state

Is there a way to extract the value from a specific tag when it is set as active? The tag in question looks like this: <li class="top-tab" role="tab" tabindex="0" aria-selected="true" aria-expanded="true"> TITLE OF SECTION </li> I am interes ...

React app experiencing inconsistent loading of Google Translate script

In my React application, I have integrated the Google Translate script to enable translation functionality. However, I am facing an issue where the translator dropdown does not consistently appear on every page visit. While it sometimes loads properly, oth ...

Navigate to a different webpage: Node.js

I've tried numerous solutions listed here, but being new to node.js, I seem to be missing a crucial part in my code. Can someone please assist me in identifying and resolving the issue? When the login button is clicked, my objective is to redirect the ...

The process of assigning a function to an object in JavaScript is currently not functioning properly

After updating a Vue2 project to Vue3, I ran into an issue with Javascript. It seems that the language now prevents me from assigning a function to an object. In the code below, I define a function "bar" within a loop. While I can successfully call the fu ...

What is the reason behind the lack of collapsing in an inline-block container that only contains floated items?

Within this design, there are 3 boxes arranged in a row using the float: left; property for each one. These boxes are contained within 2 other elements. Typically, these containers collapse due to the content being comprised of floated items. However, chan ...

Is it possible to use personalized fonts alongside Google web fonts?

I'm looking to incorporate my customized font into a website using Google's font loader. Any recommendations on how to achieve this? ...

Efficiently managing multiple database updates with PHP and JQuery

Having trouble processing multiple mySQL updates simultaneously? I have 4 select/option boxes fetching data from a db table and I want to update the database onChange using JQuery. It's working with one select module, but adding more causes issues. Th ...

Updating backgroundPosition for dual background images within an HTML element using Javascript

Issue at Hand: I am currently facing a challenge with two background images positioned on the body tag; one floating left and the other right. The image on the left has a padding of 50px on the left side, while the image on the right has a padding of 50px ...

obtain the content of a TextField element

In my React component that utilizes MaterialUI, I have created a simple form with a text field and a button: export default function AddToDo() { const classes = useStyles(); return ( <div style={{ display: "flex" }} ...

Tips and tricks for manipulating base64 images in Node.js

I have a unique challenge - I want to manipulate a base64 picture by adding just one extra pixel. My goal is to send a base64 image string (e.g. data:image/png;base64,iVBORw0KG...) from my express server. Let's say the image is 100x100px and I need to ...

Transmitting Information using Ajax and Jquery

UPDATED AND FIXED - ISSUE RESOLVED Situation: My goal is to design a webpage where clicking on an image will alter the user's settings on the server. I am attempting to utilize ajax to transmit the data without refreshing the page, and using Jquery t ...

Integrating dynamic PHP echo strings from a database into an established CSS layout

I am currently exploring PHP and MySQL databases, and I have managed to develop a basic search engine for my website. However, I am facing an issue that I am struggling to resolve: Is there a way to integrate the PHP echo output (search results) into the ...

Encapsulate ng-style within quotation marks

I am trying to hide a span if the value of filters[*index*] is empty. var span = "<span ng-style='{ 'display': filters[" + filterIndex + "] == '' ? 'none' : 'inline-block' }'></span>" cell.html ...

Divide and store parts in an array

Is there a method to split a string at a specific character and include that character in the resulting array? For instance, if we split the string "hello ??? world" at ???, the resulting array would be ["hello ", "???", "world"]. It's worth noting ...

Prevent clicks from triggering on dynamically loaded ajax content within a DIV

I am seeking help on how to prevent click events from being triggered on dynamically loaded content within a specific DIV element. <div id="left_column"> <div class="menu">------</div> <div class="menu">------</div> ...

Angular loop is unable to detect changes in the updated list

My Angular application is facing a peculiar issue that I can't seem to figure out. // Here are the class attributes causing trouble tenants: any[] = []; @Input() onTenantListChange: EventEmitter<Tenant[]>; ngOnInit(): void { this. ...

What is the best way to ensure that the width of dropdown menu items corresponds precisely to the width of the content

I have implemented the Reactstrap drop-down menu: import React from "react"; import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, } from "reactstrap"; export default class DropDownTest extends React.Component { cons ...

Steps to incorporate / insert Angular directive in an application

In my app, the main.js file is located in the root folder. -app |_ routes.js |_ main.js -components |_directives |_abc-directive.js I am trying to figure out how to define a directive that can be accessed from a different folder. This is what I at ...

Retrieve the file for saving using the HttpPost method in Asp.Net MVC

In my Asp.Net MVC project, there is a page where users can edit data loaded into a table, such as changing images, strings, and the order of items. Once all edits have been made, the client clicks on a Download button to save the resulting xml-file on the ...

Issue with my lazyloading extension for Mootools

Seeking to create a plugin for sequential image downloads using MooTools. Assuming there are images with the img tag inside a div with the class imageswrapper. The goal is to download each image in order, one after the other until all images are loaded. ...