如何在CSS中垂直对齐所有文本?

15
问题似乎在于像“g”、“y”、“q”等有向下斜坡的尾巴的某些字母,不允许垂直居中。这里有一张图片展示了这个问题a
绿色框内的字符基本上是完美的,因为它们没有向下的尾巴。红色框内的字符展示了这个问题。
我希望所有字符都能完美地垂直居中。在图像中,有向下斜坡的字符并没有垂直居中。这个问题能修复吗? 这是一个演示问题的完整代码。

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


1
这是一个有趣的问题。答案可能在这里:https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align - dwjohnston
2
在这种情况下,您需要定义什么是“中心”。对我来说,“y”实际上是居中的,而“A”可能不是。如果我们使用不同的字体族,对齐甚至会更糟,所以我们不要谈论它。 - Temani Afif
1
这就是字母的工作原理。它们是对齐的,因为 gyo 部分的 v 与大写字母的最低点在同一行上。按照您的逻辑,Å、Ä、Ö 将与 A 和 O 对齐,但实际上不行。如果您想要做一些特殊处理,需要使用 JavaScript 检查是否为小型字母,然后将字符向上移动几个字符。 - Rickard Elimää
1
我很好奇这里是否有一个有用的答案。问题似乎在于这些字母确实是居中的。也就是说,假设你可以将y和g向上移动,那么如果你有一个小写字母a怎么办?它应该如何显示? - dwjohnston
2
在任何字体编辑器中打开您的字体。编辑每个字符,直到您满意为止,使其看起来像是“居中”的样子。保存并使用作为您全新的字体系列。应该不需要超过15分钟。我认为除了使用SVG或其他使用画布等技巧的方法外,没有其他明智的方法。但我会考虑这个问题。 - Roko C. Buljan
显示剩余8条评论
4个回答

6

这是我的使用JS的解决方案。思路是将元素转换为图像,以便获取其数据作为像素,然后循环遍历它们以查找每个字符的顶部和底部,并应用翻译来修正对齐。这将适用于动态字体属性。

代码并未经过优化,但突出了主要思想:

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) {
      /* Search for 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;
          }
        }
      }
      /* Search 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;
          }
        }
      }
      /* get the difference and apply a translation*/
      var diff = (b - t)/2;
      fixes.push(diff);
      /* we apply the translation when 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>
<div class="avatar">
  <div class="character">o</div>
</div>
<div class="avatar">
  <div class="character">|</div>
</div>
<div class="avatar">
  <div class="character">@</div>
</div>
<div class="avatar">
  <div class="character">Â</div>
</div>

<div class="avatar">
  <div class="character" style="font-family:arial">Q</div>
</div>
<div class="avatar">
  <div class="character">~</div>
</div>
<div class="avatar">
  <div class="character">8</div>
</div>

<div class="avatar">
  <div class="character">ä</div>
</div>
<div class="avatar">
  <div class="character">ç</div>
</div>

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

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

更新

这是代码的第一个优化:

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;
      /* Search for the top limit */
      var t = 0;
      for (var j = 0; j < l; j+=4) {
          if (im[j+1] == 255) { /* Since we know the colors, we can only test the G composant */
            t = Math.ceil((j/4)/125);
            break;
          }
      }
      /* Search 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;
          }
      }
      /* get 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>
<div class="avatar">
  <div class="character">o</div>
</div>
<div class="avatar">
  <div class="character">|</div>
</div>
<div class="avatar">
  <div class="character">@</div>
</div>
<div class="avatar">
  <div class="character">Â</div>
</div>

<div class="avatar">
  <div class="character" style="font-family:arial">Q</div>
</div>
<div class="avatar">
  <div class="character">~</div>
</div>
<div class="avatar">
  <div class="character">8</div>
</div>

<div class="avatar">
  <div class="character">ä</div>
</div>
<div class="avatar">
  <div class="character">ç</div>
</div>

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

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

我正在使用dom-to-image插件。


运行速度有点慢,但这是目前为止第一个一直有效的解决方案。(需要注意的是,技术中心的Â看起来很奇怪。) - Brilliand
1
@Brilliand,我明确使用了 Â 来显示他的居中不是最佳的,就像我在他的答案中评论的那样 ;) 看到这个后,他可能会改变主意并接受字体的工作方式。 - Temani Afif
这看起来相当不错。如果我能让它更高效,我想我会使用它。目前它似乎有点慢。 - Chron Bag
@ChronBag 是的,你可以在代码上工作并进行优化,这是有空间的。我没有这样做,因为它可能需要很多时间,而且这并不是问题的真正目的。我想集中讨论主要思想。顺便说一下,以后可能会做。 - Temani Afif
@ChronBag 添加了一个优化,我猜会快一点。 - Temani Afif

1
也许有更好的答案,但听起来似乎唯一的方法是根据以下情况手动应用不同的样式:
  • 大写字母
  • 带尾巴的小写字母
  • 带竖杆的小写字母
  • 没有以上特征的小写字母
现在请注意,据我了解,尾巴和竖杆的相对高度是由字体定义的。我不确定是否有一种可以通过编程访问这些值的方法 - 因此您可能需要调整字体的这些值。
还要注意,这种解决方案无法支持多种语言 - 因为您需要定义每个字符在数十种不同字符集中属于哪个类别。

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>


我本来希望能避免这样的情况,但这可能是唯一的解决方案。如果没有其他更好的选择,我可能会使用它。谢谢。 - Chron Bag
2
如果该字母可以是任何Unicode字母(来自任何字母表),那么这将变得不可行。 - Brilliand
你忘记了 ÂËâë - Temani Afif
啊,是的,@Brilliand说得很对。我的用户可以使用任何Unicode文本作为他们的显示名称,所以这个解决方案更加脆弱。 - Chron Bag

0

这是一个棘手的情况!

据我所知,这将最难以本地扩展(即使用%vwvh值而不是pxem)。如果您需要在移动设备或平板电脑上美化它,请考虑使用我的解决方案和@media断点。

我的解决方案基本上检测是否为带有尾巴的小写元素,并添加一个类来抵消尾巴的高度。

在我的测试中,似乎没有需要额外处理的大写字母或没有尾巴的小写字母。如果我错了,请随时纠正我。

如果您想要更改/测试此解决方案,请参考JSFiddle

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>

请告诉我您的想法,如果我漏掉了什么,请告诉我。如果能得到最终解决方案,那将是很酷的事情,因为我自己过去也遇到过类似的问题!


这似乎类似于dwjohnston的解决方案,并且存在与他相同的缺陷。尽管如此,我还是很感激,并且如果没有更理想的解决方案出现,我可能会采用类似的方法。 - Chron Bag
作为最后的办法,你可以采用像这里所示的像素计数方法,检测出它的实际高度,然后应用适当的偏移量。 - EGC
1
是的,我在对原帖的评论中提到了那个想法。也许那就是正确的方法。 - Chron Bag
抱歉再次提醒你的想法...由于太多评论,显然错过了! - EGC

-1

你可能需要一个辅助类来完成这个任务,这样你就可以将小写字母翻译成大写字母。一个简单的脚本可以轻松地自动添加这些辅助类。

希望这能解决你的问题 :)

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


你的 y 没有居中。再微调一下! - Roko C. Buljan
我也事先不知道字母会是什么。 - Chron Bag
我理解,但是一个检查字母大小写并根据此在HTML上放置helperclass的脚本将很容易解决这个问题 :) - someone
@RagnaRoaldset 我认为他不是指大小写。他的意思是有小写字母,但字符形状有所不同。比较 oy,你就会明白。有尾巴的字符首先就会造成问题(在帖子中提到)。 - Anna
不是一个很好的想法去检测每个字符并调整它们。 - Anna
1
你可以创建一个包含所有带尾巴字母的数组,并循环遍历它,以检查div中的字母是否匹配,然后再应用类名 :)但我明白了,只是试图提出一个解决方案 :) 希望有人能想出来 :) - someone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接