ckuijjer
ckuijjer

Reputation: 13814

Vertical alignment based on x-height

When trying to center content in a container CSS-Tricks has a great guide. However when trying to vertically center some text that's just slightly smaller than its container, I think a different way of vertically centering text might be preferable. Instead of using the entire height of the font, I would rather center it based on the x-height of the font (basically the height of a lowercase x)

Visual explanation of x-height

And see this example where red is based on the entire height and green is based on the x-height

Difference between vertically centering

The only option I could come up with is to add a pseudo element to the text with the same height as the container and to use vertical-align: middle to it.

.pseudo {
  white-space: nowrap;
}

.pseudo:before {
  content: '';
  display: inline-block;
  vertical-align: middle;
  height: 100px;
  width: 0;
}

This works, but unfortunately only for a single line. I was wondering if anyone else tried to solve this issue and if perhaps there are best practices to follow? I am especially interested using as little "magic" numbers as possible and if there is a good solution for the multi line variant.

See Codepen for an example on why I want to center it based on the x-height, and my solution.

Upvotes: 25

Views: 11008

Answers (7)

Rupert Angermeier
Rupert Angermeier

Reputation: 1035

2025 Update: There is a draft for a new CSS property text-box, which is implemented in Chrome 133 and Safari 18.2.

.ex-trim {
  text-box: trim-both ex alphabetic; /* trim to x-height */
}
.cap-trim {
  text-box: trim-both cap alphabetic; /* trim to cap height */
}

See https://developer.chrome.com/blog/css-text-box-trim for a detailed explanation.

Upvotes: 1

pilau
pilau

Reputation: 6733

Ilya Streltsyn's answer (the accepted one) is amazing. Exactly the kind of thing I like about CSS - if you know the mechanics well enough, you can accomplish anything.

I have generalized the accepted answer into a reusable class, tested on Chrome, Firefox and Edge. I have also fixed issues with how it sits in the document flow (the container would carry extra width from the ::after elements, and would appear lower than sibling elements). You can easily use the class as follows:

<any class="x-height">
  <span class="x-height">
    Centered text
  </span>
</any>

And below is the Sass source (Also check it out on Codepen):

%x-height {
  content: 'x';
  display: inline-block;
  vertical-align: middle;
  width: 0;
}

.x-height {
  vertical-align: bottom;

  &::after {
    @extend %x-height;

    content: '';
    height: 100%;
  }

  > .x-height {
    display: inline-block;
    vertical-align: baseline;
    margin-left: -1ch;
    margin-right: calc(-1ch / 2);
    white-space: nowrap;

    @at-root {
      @-moz-document url-prefix() {
        & {
          margin-left: auto;
          margin-right: auto;

          &::before { display: none; }
        }   
      }
    }

    &::before, &::after {
      @extend %x-height;
      visibility: hidden;
    }

    &::after {
      width: 1ch;
      margin: 0 -1ch;
    }
  }
}

Upvotes: 1

Ilya Streltsyn
Ilya Streltsyn

Reputation: 13556

The differece between text center position and the small letters center is equal to (ascender height - x-height - descender height)/2 (basically we need to increase somehow the descender height to make it equal to ascender height - x-height to move the geometric center of the line box to the position of the small letters center). From these 3 unknowns, only x-height is available for CSS (via ex unit). Other font metrics can't be read and remain kind of 'magical numbers', so it's possible only to choose the a specific value for each specific font. But with this 'font-specific magic number' you can center any number of lines - by giving the inner element display:inline-block and assigning the magic value to its padding-bottom.

It seems impossible to get the needed value from the font metrics in pure CSS. Such vertical-align values as text-top/text-bottom can give the position of ascender or descender, but only one of them, exotic values like sub seem to be completely arbitrary, and I found no possibility to 'measure' the difference between two font metrics for one element.

My most successful attempt was the way to move the line (or lines) by half of the needed difference, making 'hybrid' centering (neither caps nor lowercase letters are centerd precisely, but 'optically' the text may look even better centered). This can be done by another pseudo element added to the last line, that has the height of the line box, but its aligned with the center of small letters:

.blue:after {
  content: ':'; /* must contain text to get the auto height of the line box */
  display: inline-block;
  vertical-align: middle;
  width: 0; /* making pseudo elenent invisible */
  overflow: hidden;
}

Edited CodePen example with the result (I didn't hide pseudo elements there for visualization).

For centering the inline-block itself, any approach can be used, I choose the approach with second helper pseudo element that always has 100% height of the container, so no more magic numbers are needed.

Hope it helps:)

Upvotes: 13

Wissam El-Kik
Wissam El-Kik

Reputation: 2525

You probably need one of these jQuery plugins:

  • FlowType: Web typography at its finest: font-size and line-height based on element width
  • Squishy: A plugin for fitting heading text to its container
  • Responsive Text: Set font sizes responsively based on its’ container width
  • TypeButter: Allows you to set optical kerning for any font on your website
  • FitText: FitText makes font-sizes flexible
  • SlabText: A jQuery plugin for producing big, bold and responsive headlines
  • Auto Line-Height: A jQuery plugin for flexible layouts

Upvotes: 0

Miguel_Velazkez
Miguel_Velazkez

Reputation: 180

Use this css

.outer {
    display: table;
    width: 200px;
    height: 200px;
    background-color:blue;
}
.inner {
    display: table-cell;
    vertical-align: middle;
    /*You can align center if you want as well*/
    /*text-align:center;*/
}
.box {
    position: relative;
    display: inline-block;
    background: orange;
    color: black;
    font-size: 60px;
    background-color: yellow;
}

And use this markup. So whatever is in the "box" div will be centered. So it is just about adding an outer to act as table and inner to act as cell so you can use the vertical align middle for multiple lines.

<div class="outer">
    <div class="inner">
        <div class="box">
            Texty</br>
            Texty
        </div>
    </div>
</div>

here is a codepen

Upvotes: 2

Jean-Fran&#231;ois Savard
Jean-Fran&#231;ois Savard

Reputation: 21004

I think the only way to get the wanted result is to use Dinamyc CSS (DCSS).

First, you will need to create a function in your website that will retrieve the height of the text (to lower case).

Second, you will need to output the css with a static position which is in reality dynamic since it is printed by your dynamic code.

Here an example pasted from this link http://www.phpsnaps.com/snaps/view/get-text-height-width/ on how to retrieve your text height in PHP :

define("F_SIZE", 8);
define("F_FONT", "arial.ttf");


function get_bbox($text){
    return imagettfbbox(F_SIZE, 0, F_FONT, $text);
}

function text_height ($text) {
    $box = get_bbox($text);
    $height = $box[3] - $box[5];
    return $height;
}

function text_width ($text) {
    $box = get_bbox($text);
    $width = $box[4] - $box[6];
    return $width;
}

And then you would echo your (x)HTML with CSS somehow like that :

echo "<span style=\"YourStyleProperty=" . **Your line height / 2 + your text height / 2 (Hint: use the PHP or equivalent if other language)** . ";\"

For more information on the imagettfbbox function :http://php.net/manual/fr/function.imagettfbbox.php

Feel free to post if you are having trouble finalizing the code, I will be glad to help if you show some efforts :).

For more info on DCSS and maybe better ideas/example don't hesitate to google DCSS.

Upvotes: 2

Lucre
Lucre

Reputation: 66

Sorry can't comment.

How about this:

.green {
  color: #6c6;
  background-color: #cfc;
  vertical-align: -16%;
  line-height: 60px;
}

http://jsfiddle.net/hcn25psh/3/

and some info which might help:

http://www.w3schools.com/cssref/pr_pos_vertical-align.asp

Upvotes: 2

Related Questions