使用CSS将非等宽字体强制转换为固定宽度

98

有没有办法使用CSS来强制字体成为等宽字体?

我的意思是,在使用非等宽字体的情况下,你能否强制浏览器以固定的宽度呈现每个字符?


浏览器无法控制这个,一切都取决于字体。所以,不,这是不可能的。使用等宽字体有什么问题吗? - Cody Gray
13
它需要与其余的排版相匹配。 - Jon Raasch
我还需要知道相反的情况,如何使用CSS强制等宽字体显示为可变宽度。 - atErik
☰和≡的宽度不同,但它们可以相同。 - Rodrigo
偶尔,浏览器确实会控制它,不幸的是,这种情况出现在新的MSIE6 - Chrome The Only One.... 在俄罗斯印刷术中,有一个数字符号 №。它应该后跟数字(法律、章节、房间等)和半个大小的空格,对于HTML建议使用  。由于它有些奇特,在MS Word中缺乏,通常根本没有空格。但是当您使一个 align=justify 文本时,像 "№1234" 这样的术语内部的间隙宽度在不同的行中变得不同!看起来很糟糕。并且要注意:Google更改了 № 符号的宽度以使文本变丑。 - Arioch 'The
11个回答

91

如果这是用于对齐表格中的数字,其中一些字体(具有传统排版)在默认情况下以可变宽度呈现它们(例如Windows上的Segoe UI),您应该研究以下CSS属性:

font-variant-numeric: tabular-nums;

(这将禁用 numeric-spacing 变体的 proportional-nums 默认值,该变体至少由 OpenType 字体支持,并且可能由您特定平台上的 Web 浏览器的文本渲染器支持的其他字体格式支持)

无需 JavaScript!这是禁用这些字体中可变宽度字形并强制使用表格数字的最清晰的方式(通常使用相同字体中的相同字形,但其前导和尾随间隙增加,以便 0 到 9 的 10 个数字将以相同宽度呈现;然而,某些字体可能会避免视觉上可变的数字间距,并略微扩大某些数字,或者在数字 1 的底部添加底横线)。

请注意,这不会禁用 Segoe UI 观察到的可变高度(例如,某些数字只有 x 高度,如小写字母,而其他数字具有上伸部分或下降部分)。这些传统数字形式也可以使用 CSS 禁用。

font-variant-numeric: lining-nums;

(这将禁用默认的oldstyle-nums值,该值至少由OpenType字体支持,并且可能由您特定平台的Web浏览器的文本渲染器支持的其他字体格式支持的numeric-figure变体)

您可以同时结合使用:

font-variant-numeric: tabular-nums lining-nums;

--

下面的片段演示了如何使用单个比例字体(而不是等宽字体!)来显示数字的形状变体,例如在Windows上的'Segoe UI',并展示了产生的不同水平和垂直对齐方式。
请注意,如果应用了不同的样式(如粗体或斜体),而不是中等罗马体,这种样式不会禁止数字改变宽度,因为它们将使用具有自己独特度量的不同字体(这也不能保证适用于所有等宽字体)。

html { font-family: 'Segoe UI'; /* proportional with digit variants */ }
table { margin: 0; padding: 0; border: 1px solid #AAA; border-collapse: collapse; }
th, td { vertical-align:top; text-align:right; }
.unset       { font-variant-numeric: unset; }
.traditional { font-variant-numeric: proportional-nums oldstyle-nums; }
.lining      { font-variant-numeric: proportional-nums lining-nums;   }
.tabular-old { font-variant-numeric: tabular-nums      oldstyle-nums; }
.tabular-new { font-variant-numeric: tabular-nums      lining-nums;   }
.normal      { font-variant-numeric: normal; }
<table>
<tr><th>unset<td><table width="100%" class="unset">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>traditional<td><table width="100%" class="traditional">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>lining<td><table width="100%" class="lining">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>tabular-old<td><table width="100%" class="tabular-old">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>tabular-new<td><table width="100%" class="tabular-new">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>normal<td><table width="100%" class="normal">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
</table>

参考:https://developer.mozilla.org/docs/Web/CSS/font-variant-numeric

请注意,对于'Segoe UI'字体来说,'normal'样式与'tabular-new'样式相同(即'tabular-nums lining-nums')。并非所有定义数字变体的字体都是如此,并且并非所有数字系统都是如此(如果不显示拉丁数字,例如某些原生婆罗门数字,特别是如果它们来自不支持所有这些样式的回退字体)。此外,这对于非数字字符没有影响,它们保留其可变宽度,但可以通过其他CSS属性或使用编码变体控制来定义特定的变体。

如果您使用罗马数字系统中的数字,这些数字可能仍然以可变宽度呈现(例如,1968年的"MCMLXVIII"),除非HTML包含一些CSS样式,允许将它们的语义更改为数字而不是字母,或者这些数字以不同的方式编码,例如在CJK字体中具有"fullwidth"变体,或者在数学字体中定义了具有特定代码点的特定样式变体,而不是使用样式和标记。


1
我之前不知道这个CSS属性,谢谢。遗憾的是,Edge和IE不支持它。 - Blender
显然,数字样式的默认选项取决于浏览器:在Chrome上,定义传统数字形式的字体默认启用这些功能,您需要使用这些CSS样式在数字表中禁用它。IE和Edge似乎默认禁用这些字体功能,不允许我们通过CSS启用它们。 - verdy_p
1
这也可以通过font-feature-settings: 'tnum' 1;font-feature-settings:'tnum' on;进行设置,参见https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings ,但请注意MDN的说明: “如果有可能,Web作者应该改用[...]font-variant-numeric”。 - mrienstra
“font-feature-settings:” 只有在您绝对确定文本将使用 Opentype 字体呈现时才可行。请注意,CSS 不仅关注 OpenType,还有其他字体技术(包括特别是 SVG 字体,以及 PostScript 字体:考虑打印,特别是在 Linux 上使用 CUPS 支持许多带有基于 PostScript 渲染器的打印机,通常是 Ghostscript,或者渲染到 PDF,即使现在 PDF 也支持 OpenType)。 “font-feature-settings:” 只是低级功能,用于支持各种 OpenType 功能,包括许多自定义、非标准或实验性功能。 - verdy_p
此外,“font-variant-numeric:”高级属性可以用于非拉丁数字或草书风格中的拉丁文脚本(可能需要激活或更改其他OpenType功能,如自定义连字),或其他符号(例如在西里尔语中)。以强制使用位置小数系统或禁用某些与语言相关的“国家/传统数字”的替换。在表格化的数字数据(会计、统计、游戏/运动得分、排名等)中考虑使用时,不存在通用解决方案。 - verdy_p
显示剩余3条评论

16

为什么不打破常规,使用表格来解决这个问题:

<table cellpadding="0" cellspacing="0">
<tr><td>T</td><td>h</td><td>e</td><td></td><td>r</td><td>a</td><td>i</td><td>n</td><td></td><td>i</td><td>n</td><td></td><td>S</td><td>p</td><td>a</td><td>i</td><td>n</td><td></td><td>s</td><td>t</td><td>a</td><td>y</td><td>s</td></tr>
<tr><td>m</td><td>a</td><td>i</td><td>n</td><td>l</td><td>y</td><td></td><td>i</td><td>n</td><td></td><td>t</td><td>h</td><td>e</td><td></td><td>p</td><td>l</td><td>a</td><td>i</td><td>n</td><td>s</td><td>.</td></tr>
</table>

111
那甚至更加“墨守成规”(抱歉,我得这么说)。 - Juan Cortés
86
我认为我们现在应该搁置这个想法。 - David J.
78
你们所有人因为发表这些言论应该被关进牢房。 - roncli
1
就像CSS技巧中将字符串拆分为单个字符并放入内联块中一样,您可以水平居中每个字符。这看起来非常可怕,除非您使用等宽字体(如果您已经使用等宽字体,则最好不使用此拆分技巧,即使使用一些额外的“字符间距”)。 - verdy_p
1
为什么不重新发明轮子并在画布上呈现整个网页?我的意思是,这是确保在不同的浏览器中完美对齐文本的一种可靠方式。 - Jack G
显示剩余2条评论

12
无法使用CSS实现此效果。即使可以,结果也会很糟糕: enter image description here 如果确实需要这样做,可以使用JavaScript将每个字符包装在一个元素中(或手动执行)。
function wrap_letters($element) {
    for (var i = 0; i < $element.childNodes.length; i++) {
        var $child = $element.childNodes[i];

        if ($child.nodeType === Node.TEXT_NODE) {
            var $wrapper = document.createDocumentFragment();

            for (var i = 0; i < $child.nodeValue.length; i++) {
                var $char = document.createElement('span');
                $char.className = 'char';
                $char.textContent = $child.nodeValue.charAt(i);

                $wrapper.appendChild($char);
            }

            $element.replaceChild($wrapper, $child);
        } else if ($child.nodeType === Node.ELEMENT_NODE) {
            wrap_letters($child);
        }
    }
}

wrap_letters(document.querySelectorAll('.boxes')[0]);
wrap_letters(document.querySelectorAll('.boxes')[1]);
.char {
    outline: 1px solid rgba(255, 0, 0, 0.5);
}

.monospace .char {
    display: inline-block;
    width: 15px;
    text-align: center;
}
<h2 class="boxes">This is a title</h2>
<h2 class="boxes monospace">This is a title</h2>


13
那看起来真的很丑。 - thirtydot
imo使用text-align更好看:http://jsfiddle.net/f7TPf/3/,但仍然相当丑陋。 - Oleg
4
@Blender, 总体而言,你是正确的,但有一些情况下,等宽字体实际上会增加加粗和斜体等字体属性的宽度,如果你需要它们对齐的话,这可能是不好的。Lucida Console就是这样一种字体。如果你想使用语法突出显示进行加粗,在IDE中使用它是无用的,因为Lucida Console 12pt在加粗时大约比普通字体宽1像素。这使得通过空格将代码对齐变得不可能(超出行首的空格)。我发现有一个IDE聪明到足以在复选框上锁定粗体的纯宽度。 - user4229245
5
我知道这很古老,但我觉得有必要提到它的用途,例如在计数器和图标字体中会用到。 - Ian
据我所知,如果更改任何字体样式(如粗体和斜体),在所有分辨率下,甚至使用为 UI 设计的字体(屏幕上),都无法预期文本(甚至仅数字)将具有相同的度量。简单地说,许多字体不支持所有样式范围,并且可能仍然使用不相关的字体(包括由于缺乏您需要的完整可编码聚类集的覆盖而使用)。 - verdy_p
CSS没有办法强制任何字形以预定义的单元格大小呈现(即使在CJK中,或使用通用的“等宽字体”字体族,通常很丑)。您需要坚持一种样式,并通过单独添加其他符号来区分数字(例如颜色、额外的图标或标点/符号),如果您想在某些数字上加重一些强调(并将它们适当地放置在右侧或左侧,具体取决于对齐方向,或者在单独的数据列内部)。 - verdy_p

11

我刚发现了 text-transform: full-width; 这个实验性的关键字,它可以:

[...] 强制字符写在方块内 [...]

text-transform | MDN

与负的 letter-spacing 结合使用,你就能得到不那么糟糕的结果:

Fixed-width sans-serif font to mock monospaced font

<style>
pre {
  font-family: sans-serif;
  text-transform: full-width;
  letter-spacing: -.2em;
}
</style>

<!-- Fixed-width sans-serif -->
<pre>
. i I 1  | This is gonna be awesome.
ASDFGHJK | This is gonna be awesome.
</pre>

<!-- Default font -->
. i I 1  | This is gonna be awesome.
<br>
ASDFGHJK | This is gonna be awesome.

4
目前只能在Firefox中使用,但如果它得到广泛支持,这将是最好的答案。 - Josh Powlison
1
这种方法对于许多在Unicode中没有完全编码为全角形式的字符是无效的(可以通过text-transform映射的全宽变体集合非常小,主要只有ASCII和一些亚洲标点符号和货币符号)。全宽变体仅为了与旧编码兼容而被编码到Unicode中,但正确的编码方式是分别使用普通字母和样式文本。 - verdy_p
2
可用的子集仅包括: 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z , . : ; ! ? " ' ` ^ ~  ̄ _ & @ # % + - * = < > ( ) [ ] { } ⦅ ⦆ | ¦ / \ ¬ $ £ ¢ ₩ ¥ 因此,请勿指望正确地呈现“café”。对于印度文字、阿拉伯文、希伯来文、西里尔文、泰文等,没有解决方案。 - verdy_p
这只适用于非常基础的英语,甚至无法处理罗马化的日语(缺少长音符号)或罗马化的中文(缺少声调符号),或韩语(即使有韩元货币符号)。 - verdy_p

6

好的,你没有说只使用CSS。通过使用一点Javascript将每个字母包装在标签中,这样做是可行的。剩下的就用CSS来完成...

window.onload = function() {
  const secondP = document.getElementById('fixed');
  const text = secondP.innerText;
  const newText = text.split('').map(c => {
    const span = `<span>${c}</span>`;
    return span;
  }).join('');
  secondP.innerHTML = newText;
}
p {
  position: relative;
  border: 1px solid black;
  border-radius: 1em;
  padding: 1em;
  margin: 3em 1em;
}

p::after {
  content: attr(name);
  display: block;
  background-color: white;
  color: green;
  padding: 0 0.5em;
  position: absolute;
  top: -0.6em;
  left: 0.5em;
}

#fixed span {
  display: inline-block;
  width: 1em;
  text-align: center;
}
<p id="variable" name="Variable Width">It might not look nice, but with a little Javascript, I can force a variable width font to act like a fixed-width font.</p>
<p id="fixed" name="Fixed Width">It might not look nice, but with a little Javascript, I can force a variable width font to act like a fixed-width font.</p>

在普通文本段落中使用这种技术看起来很糟糕,但有时候这是有意义的。图标字体和Unicode符号都可以利用这种技术。

我在寻找一种解决方案来解决Unicode符号替换其他Unicode符号时将常规文本向右移动的问题时发现了这个问题。


是的,使用居中表格单元格、内联块或JavaScript拆分字符串时,会产生可怕的视觉效果。 但是,在制表数据列中,有一些排版正确的方法可以增强渲染,特别是对于数字(请参见下文:无HTML技巧,无JavaScript,仅一个CSS属性来定义,可能还需要另一个CSS属性来对齐变量精度和范围的非整数数字的小数点)。 - verdy_p

0

有时候我为倒计时做了一件非常漂亮的事情:

HTML:

<div class="counter">
    <span class="counter-digit counter-digit-0">2</span>
    <span class="counter-digit counter-digit-1">4</span>
    <span class="counter-digit counter-digit-divider">/</span>
    <span class="counter-digit counter-digit-2">5</span>
    <span class="counter-digit counter-digit-3">7</span>
</div>

SCSS:

$digit-width: 18px;
.counter {

    text-align: center;
    font-size: $digit-width;

    position: relative;

    width : $digit-width * 4.5;
    margin: 0 auto;
    height: 48px;
}
.counter-digit {

    position: absolute;
    top: 0;
    width: $digit-width;
    height: 48px;
    line-height: 48px;
    padding: 0 1px;

    &:nth-child(1) { left: 0; text-align: right; }
    &:nth-child(2) { left: $digit-width * 1; text-align: left;}
    &:nth-child(3) { left: $digit-width * 2; text-align: center; width: $digit-width / 2} // the divider (/)
    &:nth-child(4) { left: $digit-width * 2.5; text-align: right;}
    &:nth-child(5) { left: $digit-width * 3.5; text-align: left;}
}

0
你可以将秒数用标签包裹起来,并使用以下样式... .time-seconds {display: inline-block;width: .52em;text-align: center;} 查看示例代码。

function padlength(what) {
  var output = (what.toString().length == 1) ? "0" + what : what;
  return output;
}

function displaytime() {
  var serverdate = new Date();
  var dd = "am";
  var hh = serverdate.getHours();
  var h = hh;
  if (h >= 12) {
    h = hh - 12;
    dd = "pm";
  }
  if (h == 0) {
    h = 12;
  }
  h = parseInt(h);
  var sec = String(padlength(serverdate.getSeconds()));
  var timeFixed = h + ':' + padlength(serverdate.getMinutes()) + ':<span class="time-seconds">' + sec.charAt(0) + '</span><span class="time-seconds">' + sec.charAt(1) + '</span> ' + dd;
  timeVariable = h + ':' + padlength(serverdate.getMinutes()) + ':' + sec + ' ' + dd;
  document.getElementById("servertime-fixed").innerHTML = timeFixed;
  document.getElementById("servertime-variable").innerHTML = timeVariable;
}

window.onload = function() {
  displaytime();
  setInterval("displaytime()", 1000);
};
center {
  font-size: 3em;
  font-family: Cursive;
}

.time-seconds {
  display: inline-block;
  width: .52em;
  text-align: center;
}
<html>

<body>
  <center id="servertime-fixed">H:MM:SS mm</center>
  <center id="servertime-variable">H:MM:<span class="time-seconds">S</span><span class="time-seconds">S</span> mm</center>
</body>

</html>


将宽度设置为接近0会让我顺利进行,谢谢! - Justin

0
我刚遇到了同样的问题。我的字体不支持font-variant-numeric: tabular-nums(这一点我知道),而其他解决方案也不适合我,所以我想出了这个方法——在我的情况下,我只需要扩大字母间距,然后压缩(巨大的)零,使其看起来可接受:

CSS:

.squashzeros { letter-spacing:.2em; }
.squashzeros span { display:inline-block; margin:0 -.09em; }

JS:

document.querySelectorAll('.squashzeros').forEach((o)=>{
        o.innerHTML = o.innerText.replaceAll(/0/g,'<span>0</span>');
    });

很遗憾,我没有找到纯CSS的解决方案。


如果您使用任何具有可变宽度数字的字体,请使用另一种字体。您现代浏览器中的标准字体都支持固定宽度数字。如果您仍然使用比例字体,则更简单的方法是使包含数字的文本跨度使用“font:monospace”,而无需任何Javascript和视觉重排,页面加载后(重排在导航时很痛苦,您无法可靠地单击,必须等待页面加载完成并运行所有脚本,包括上面的脚本)。在大型数据表中使用表格数字的典型用法需要长时间加载和转换。 - verdy_p
@verdy_p 当然,这是一个可怕的折衷办法,但有时候你只需要坚持某种字体 - 没有任何借口。当然,没有人应该在大型表格上做这样的事情,那将是非常疯狂的! - nïkö

-2

结合了Márton Tamásnïkö的回答:

document.querySelectorAll('pre').forEach( o => {
  o.innerHTML = o.innerText.replace(/(.)/g, '<i>$1</i>');
});
pre i {
  font-style: normal;
  font-family: serif;
  display: inline-block;
  width: 0.65em;
  text-align: center;
}
<!-- Fixed-width serif -->
<pre>
. i I 1  | This is gonna be awesome. 12:10
ASDFGHJK | This is gonna be awesome. 08:51
</pre>

<!-- Default font -->
. i I 1  | This is gonna be awesome. 12:10
<br>
ASDFGHJK | This is gonna be awesome. 08:51


这需要Javascript来转换文档内容(重新排版较慢)。不幸的是,该脚本枚举字符,但会打断一些国际文本中不可分割的序列。它对西方字母表和简单音节文字有效,对闪米特语字母表、印度文字表和混合多种书写规则的CJK语言(在“象形正方形”中使用不同的布局规则,包括传统数字或罗马数字的呈现)效果不佳。对于东南亚文字,指标不正确,在藏文、缅甸文、高棉文、彝文和古蒙古文上会出现问题。 - verdy_p

-3
不,除非它是真正的等宽字体。

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