CSS: 在不改变容器大小的情况下加粗某些文本

我有一个水平导航菜单,基本上就是一个 <ul>,元素并排设置。我没有定义宽度,只是简单地使用填充,因为我希望宽度由菜单项的宽度来定义。我加粗了当前选定的项。

麻烦的是,在粗体中,单词会变得稍微宽一些,这会导致其他元素稍微向左或向右移动。有什么聪明的办法可以阻止这种情况发生吗?类似于告诉填充物忽略粗体引起的额外宽度?我的第一个想法是简单地从“活动”元素的填充中减去几个像素,但这个数量是不同的。

如果可能的话,我希望避免在每个条目上设置一个静态宽度,然后与目前的填充解决方案相对应地居中,以便将来对条目的更改变得简单。

85370 次浏览

有趣的问题,我猜你用的是浮动,对吧?

好吧,我不知道任何技术,你可以用来摆脱这种字体放大,因此他们将尝试适合最小宽度要求-和不同的字体厚度将改变这个值。

我所知道的避免这种变化的独特解决方案是你说你不想要的: 设置固定大小的 Li’s。

不幸的是,当文本为粗体时,避免宽度变化的唯一方法是定义列表项的宽度,但是正如您所说的那样,手动定义宽度非常耗费时间,而且不可伸缩。

我唯一能想到的是使用一些 javascript 来计算选项卡在加粗之前的宽度,然后在需要加粗的同时应用该宽度(无论是在鼠标悬停还是单击时)。

使用 JavaScript 根据未加粗的内容设置 li 的固定宽度,然后通过对 <a>标记应用样式来加粗内容(或者如果 <li>没有任何子元素,则添加 span)。

我有同样的问题,但得到了一个类似的效果与一个小妥协,我使用文本阴影代替。

li:hover {text-shadow:0px 0px 1px black;}

下面是一个实际的例子:

body {
font-family: segoe ui;
}


ul li {
display: inline-block;
border-left: 1px solid silver;
padding: 5px
}


.textshadow :hover {
text-shadow: 0px 0px 1px black;
}


.textshadow-alt :hover {
text-shadow: 1px 0px 0px black;
}


.bold :hover {
font-weight: bold;
}
<ul class="textshadow">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li><code>text-shadow: 0px 0px 1px black;</code></li>
</ul>


<ul class="textshadow-alt">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li><code>text-shadow: 1px 0px 0px black;</code></li>
</ul>


<ul class="bold">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li><code>font-weight: bold;</code></li>
</ul>

举个例子

你可以像 amazon.com 那样实现“逐个部门购物”悬浮菜单。它使用宽 div。您可以创建宽 div 并隐藏它的正确部分

我发现当你把字母间距调整1px 时,大多数字体都是相同的大小。

a {
letter-spacing: 1px;
}


a:hover {
font-weight: bold;
letter-spacing: 0px;
}

虽然这确实改变了常规字体,使每个字母有一个额外的像素间距。对于菜单,标题是如此的短,以至于不会出现问题。

使用: : after 的最佳工作解决方案

超文本标示语言

<li title="EXAMPLE TEXT">
EXAMPLE TEXT
</li>

CSS

li::after {
display: block;
content: attr(title);
font-weight: bold;
height: 1px;
color: transparent;
overflow: hidden;
visibility: hidden;
}

它添加了一个不可见的伪元素,该元素具有粗体文本宽度,由 title属性提供。

text-shadow解决方案在 Mac 上看起来不自然,没有利用 Mac 上文本渲染提供的所有美感. . :)

Http://jsfiddle.net/85lbg/

提供者: https://stackoverflow.com/a/20249560/5061744

经典-任何字体,广泛的支持

最便携和视觉上令人满意的解决方案将使用 text-shadow 索尔盖尔的回答与阿托尔斯科的评论一致结合 -webkit-text-stroke-width 奥利弗的回答

li:hover   { text-shadow: -0.06ex 0 0 currentColor, 0.06ex 0 0 currentColor; }
@supports (-webkit-text-stroke-width: 0.04ex) { /* 2017+, mobile 2022+ */
li:hover { text-shadow: -0.03ex 0 0 currentColor, 0.03ex 0 0 currentColor;
-webkit-text-stroke-width: 0.04ex; }
}

这会在每个字符的两边使用微小的“阴影”字体的当前颜色使用单位,将适当缩放与字体渲染。如果有 浏览器支持,阴影就减半,我们增加用于绘制文本的笔画宽度。这看起来有点干净,因为阴影块状没有模糊半径和笔触模糊在较高的水平(见下面的演示)。

warning警告:虽然 px值支持十进制值,但是当字体大小改变时(例如用户使用 Ctrl + +缩放视图) ,它们看起来就不那么好了。使用相对值代替。

这个答案使用 ex单位的分数,因为它们按字体缩放。
在大多数浏览器中,默认的字体是“”> < b > * “将这个解释与前面的‘ ex’链接的1em = 2ex”结合起来 ,预计 1ex8px,因此 0.025ex0.1px

现代变量字体

现在有一些新的 可变字体能够通过 font-variation-settings改变 字体等级。你需要使用特定字体的 浏览器支持(自2018年以来一直很好)和 还有支持(现在仍然很少)。

@import url("https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght,GRAD@8..144,400,45;8..144,400,50;8..144,1000,0&family=Roboto+Serif:opsz,GRAD@8..144,71&display=swap");
li { font-family:Roboto Flex, sans-serif; }


/* Grade: Increase the typeface's relative weight/density */
@supports (font-variation-settings: 'GRAD' 150) {
li:hover { font-variation-settings: 'GRAD' 150; }
}
/* Text Shadow: Failover for pre-2018 browsers */
@supports not (font-variation-settings: 'GRAD' 150) {
li:hover { text-shadow: -0.06ex 0 0 currentColor, 0.06ex 0 0 currentColor; }
}

这将加载一个可变字体,然后在支持此类字体设置的浏览器中,指示浏览器在悬停时以粗体级别呈现该字体。传统的解决方案(没有笔画,因为这些都是新的可变字体支持)是作为老式浏览器的故障转移提供的,但是自2018年以来,字体等级已经得到了相当普遍的支持,所以这应该不再是必要的。

为了完整性(因为这确实影响渲染的宽度) ,可变字体还支持模拟权重(粗体) ,这与 font-weight中规定的权重形成对比。无论使用哪种方法,您都受制于字体的粒度。虽然支持 wght变体的可变字体允许全谱的权重,但大多数字体要么缺乏粗体变体,要么只有一种。系统呈现的需要渲染一个字体为粗体将自己做它需要,但只有在一个权重(细节和例子在这里)。一些非变量字体提供了一些权重,如 机器人,在下面的演示中使用。在演示中使用滑块查看粒度差异。

比较六种方法的演示

(不要被庞大的代码块吓倒,这些代码块主要用于实现交互式滑块,并比较这个问题的答案所提供的所有方法。)

@import url("https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900");
@import url("https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght,GRAD@8..144,400,45;8..144,400,50;8..144,1000,0&family=Roboto+Serif:opsz,GRAD@8..144,71&display=swap");


body { font-family: 'Roboto'; }
.v   { font-family: 'Roboto Flex'; } /* variable! */


/* For parity w/ shadow, weight is 400+ (not 100+) & grade is 0+ (not -200+) */
li:hover { text-shadow: initial !important; font-weight: normal !important;
-webkit-text-stroke-width: 0 !important; }
.weight  { font-weight: calc(var(--bold) * 500 + 400); }
.shadow, .grade { text-shadow: calc(var(--bold) * -0.06ex) 0 0 currentColor,
calc(var(--bold) * 0.06ex) 0 0 currentColor; }
ul[style*="--bold: 0;"] li { text-shadow:none; } /* none < zero */
.stroke   { -webkit-text-stroke-width: calc(var(--bold) * 0.08ex); }
.strokshd { -webkit-text-stroke-width: calc(var(--bold) * 0.04ex);
text-shadow: calc(var(--bold) * -0.03ex) 0 0 currentColor,
calc(var(--bold) * 0.03ex) 0 0 currentColor; }


.after span   { display:inline-block; font-weight: bold; } /* @SlavaEremenko */
.after:hover span  { font-weight:normal; }
.after span::after { content: attr(title); font-weight: bold;
display: block; height: 0; overflow: hidden; }
.ltrsp        { letter-spacing:0px; font-weight:bold; } /* @Reactgular */
.ltrsp:hover  { letter-spacing:1px; }


@supports (font-variation-settings: 'GRAD' 150) { /* variable font support */
:hover { font-variation-settings: 'GRAD' 0 !important; }
.weight.v { font-weight: none !important;
font-variation-settings: 'wght' calc(var(--bold) * 500 + 400); }
.grade { text-shadow: none !important;
font-variation-settings: 'GRAD' calc(var(--bold) * 150); }
}
Boldness: <input type="range" value="0.5" min="0" max="1.5" step="0.01"
style="height: 1ex;"
onmousemove="document.getElementById('dynamic').style
.setProperty('--bold', this.value)">


<ul style="--bold: 0.5; margin:0;" id="dynamic">
<li class=""        >MmmIii123 This tests regular weight/grade/shadow</li>
<li class="weight"  >MmmIii123 This tests the slider (weight)</li>
<li class="weight v">MmmIii123 This tests the slider (weight, variable)</li>
<li class="grade v" >MmmIii123 This tests the slider (grade, variable)</li>
<li class="shadow"  >MmmIii123 This tests the slider (shadow)</li>
<li class="stroke"  >MmmIii123 This tests the slider (stroke)</li>
<li class="strokshd">MmmIii123 This tests the slider (50/50 stroke/shadow)</li>
<li class="after"><span title="MmmIii123 This tests [title]"
>MmmIii123 This tests [title]</span> (@SlavaEremenko)</li>
<li class="ltrsp"   >MmmIii123 This tests ltrsp (@Reactgular)</li>
</ul>

将鼠标悬停在呈现的行上,查看它们与标准文本的不同之处。(这颠倒了悬浮文本加粗的意图,这样我们可以更容易地比较不同的方法。)移动大胆的滑块周围(滑块控制的条目)或改变你的浏览器的缩放水平(Ctrl + +Ctrl + -) ,看看他们如何变化。

注意当可变权重是连续的时候,(非可变)权重是如何在四个离散的步骤中进行调整的。

我在这里添加了另外两个解决方案进行比较: @ Reactgular 的字母间距魔术@ SlavaEremenko 的 ::after绘画技巧,前者不太好用,因为它涉及到猜测字体宽度范围,后者留下了笨拙的额外空间,这样粗体文本可以在不触及相邻文本项的情况下扩展(我将属性放在粗体文本后面,这样你就可以看到它是如何不移动的)。

更新: 必须使用标题的 B 标签,因为在 IE11的伪类 i: after 没有显示当我有可见性: 隐藏。

在我的例子中,我希望将(自定义设计的)输入复选框/单选框与标签文本对齐,当选中输入时,文本变成粗体。

这里提供的解决方案在 Chrome 中不起作用。 输入和标签的垂直对齐被: 之后 psuedo 类和-页边距没有修复这个问题。

这里有一个修复方法,您不会在垂直对齐方面遇到麻烦。

/* checkbox and radiobutton */
label
{
position: relative;
display: inline-block;
padding-left: 30px;
line-height: 28px;
}


/* reserve space of bold text so that the container-size remains the same when the label text is set to bold when checked. */
label > input + b + i
{
font-weight: bold;
font-style: normal;
visibility: hidden;
}


label > input:checked + b + i
{
visibility: visible;
}


/* set title attribute of label > b */
label > input + b:after
{
display: block;
content: attr(title);
font-weight: normal;
position: absolute;
left: 30px;
top: -2px;
visibility: visible;
}


label > input:checked + b:after
{
display: none;
}


label > input[type="radio"],
label > input[type="checkbox"]
{
position: absolute;
visibility: hidden;
left: 0px;
margin: 0px;
top: 50%;
transform: translateY(-50%);
}


label > input[type="radio"] + b,
label > input[type="checkbox"]  + b
{
display: block;
position: absolute;
left: 0px;
margin: 0px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
background-color: #00a1a6;
border-radius: 3px;
}


label > input[type="radio"] + b
{
border-radius: 50%;
}


label > input:checked + b:before
{
display: inline-block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
content: '';
border-width: 0px 3px 3px 0px;
border-style: solid;
border-color: #fff;
width: 4px;
height: 8px;
border-radius: 0px;
}


label > input[type="checkbox"]:checked + b:before
{
transform: translate(-50%, -60%) rotate(45deg);
}


label > input[type="radio"]:checked + b:before
{
border-width: 0px;
border-radius: 50%;
width: 8px;
height: 8px;
}
<label><input checked="checked" type="checkbox"/><b title="Male"></b><i>Male</i></label>
<label><input type="checkbox"/><b title="Female"></b><i>Female</i></label>

要获得更新的答案,可以使用 -webkit-text-stroke-width:

.element {
font-weight: normal;
}


.element:hover {
-webkit-text-stroke-width: 1px;
-webkit-text-stroke-color: black;
}

这样可以避免任何伪元素(这对于屏幕阅读器来说是一个优势)和文本阴影(看起来杂乱无章,但仍然可以产生轻微的“跳跃”效果)或设置任何固定宽度(这可能是不切实际的)。

它还允许你设置一个大于1px 的元素(理论上,你可以设置一个你喜欢的大胆字体,也可以设置一个没有大胆变体的大胆字体版本,比如自定义字体(可变字体贬低这个建议))。尽管这是应该避免的,因为它可能会使一些字体显得粗糙和参差不齐)

我肯定在 Edge,Firefox,Chrome 和 Opera (在发布的时候) 还有在 Safari(编辑:@Lars Blumberg 谢谢你证实这一点)中都可以使用。它不工作在 IE11或以下。

还要注意的是,它使用的是 -webkit前缀,所以这不是标准的,将来可能会丢掉支持,所以不要依赖于这个大胆的设计非常重要——最好避免使用这种技术,除非它只是为了美观。

这是一个非常古老的问题,但是我重新考虑这个问题,因为我在开发一个应用程序时遇到了这个问题,并且发现这里所有的答案都有问题。

(对于 TL,跳过这一段; DR。.) 我使用的是来自 cloud.typography.com 的哥谭 webfont,我有一些按钮,它们从空心开始(有白色边框/文本和透明背景) ,并在悬停时获得背景颜色。我发现我使用的一些背景颜色与白色文本对比不好,所以我想把那些按钮的文本改为黑色,但是ーー不管是因为视觉上的技巧还是常见的反锯齿方法ーー浅色背景上的深色文本总是显得比深色背景上的白色文本轻。我发现将深色文本的权重从400增加到500保持了几乎完全相同的“视觉”权重。然而,它只是将按钮的宽度增加了很小一部分ーー只是像素的一小部分ーー但却足以让按钮看起来略微“抖动”一下,我想把它去掉。

解决方案:

显然,这是一个非常棘手的问题,因此需要一个非常棘手的解决方案。最终,我使用了一个负 letter-spacing的粗体文本 cgTag 以上建议,但1px 将是过分,所以我只是计算了确切的宽度,我需要。

通过检查 Chrome devtools 中的按钮,我发现我的按钮的默认宽度是165.47 px,悬停时是165.69 px,相差0.22 px。这个按钮有9个字符,所以:

0.22/9 = 0.024444 px

通过将其转换为 em 单位,我可以使调整字体大小不可知。我的按钮使用的字体大小是16px,所以:

0.024444/16 = 0.001527 em

因此,对于我的特定字体,下面的 CSS 保持按钮 没错的悬停宽度相同:

.btn {
font-weight: 400;
}


.btn:hover {
font-weight: 500;
letter-spacing: -0.001527em;
}

通过一些测试和使用上面的公式,您可以找到正确的 letter-spacing值适合您的情况,它应该工作而不管字体大小。

需要注意的一点是,不同的浏览器使用的亚像素计算方法略有不同,因此,如果你的目标是达到亚像素完美精度的 OCD 级别,你需要重复测试,并为每个浏览器设置不同的值。以浏览器为目标的 CSS 样式通常不受欢迎,但是我认为这是唯一有意义的选择。

我强烈建议在这个用例中尝试使用 单宽字体。

它们仍然是成比例的字体(相对于单一空格) ,但它们是 在不同的字体大小上占据相同的大小

下面是来自 这篇优秀的文章的一个例子,比较了比例字体的 IBM Plex Sans 和 uniwidth Recursive。

comparing proportional to uniwidth font

可以尝试的免费单宽字体有:

还有许多通过上述文章链接的非免费选项。

为了提供另一种解决方案(因为我也想要文本阴影) ,我可以用以下方法来实现:

.menu {
display: flex;
justify-content: space-between;
font-family: sans-serif;
}
.text-shadow {
padding: 0 0.375rem;
}
.text-shadow:first-of-type,
.text-shadow:last-of-type {
padding: 0 0.075rem;
}
.text-shadow:hover {
text-shadow: 1px 1px white;
font-weight: 900;
padding: 0;
}
<nav class="menu">
<div class="text-shadow">Item 1</div>
<div class="text-shadow">Item 2</div>
<div class="text-shadow">Item 3</div>
</nav>

我花了几个小时在这里浏览答案,但是对于 text-shadow-webkit-text-stroke-width解决方案、 content:attr等等的渲染质量感到不满意。

因此,这里有一个 JS 的替代方案,当鼠标悬停或点击时,它会显示一个干净的粗体和放大的字体。垂直菜单版本在 Codeply 给你上,水平版本是 给你。在这两种情况下,注释掉对 fixElementSize的调用就可以看出区别: 通过调用,菜单项的定位依然坚如磐石。

困难的工作是通过这个函数来完成的(用‘ height’代替‘ width’代替水平版本) ,但是它需要 box-sizing:border-boxdisplay:block:

/**
* Set all anchor tags below 'myclass' to have the same fixed height.  Note
* that 'getBoundingClientRect().height' gives the same integral result as
* 'offsetHeight' for display:block
*/
function fixElementSize(myclass) {
var atag1 = document.querySelector(myclass + ' a');
var height = atag1.offsetHeight;
var nlist = document.querySelectorAll(myclass + ' a');
for (let i = 0; i < nlist.length; i++)
nlist[i].style.height = height + 'px';
}