我有一个水平导航菜单,基本上只是一个 <ul>
,其中的元素并排设置。我没有定义宽度,而是使用填充,因为我希望宽度由菜单项的宽度定义。我加粗当前选定的项目。
问题在于加粗会使单词略微变宽,这会导致其余元素略微向左或向右移动。有没有巧妙的方法可以防止这种情况发生?例如告诉填充忽略加粗导致的额外宽度?我的第一个想法是从“活动”元素的填充中减去几个像素,但这个量是变化的。
如果可能,我想避免对每个条目设置静态宽度,然后居中,而是使用我目前拥有的填充解决方案,以便将来更改条目变得简单。
我有一个水平导航菜单,基本上只是一个 <ul>
,其中的元素并排设置。我没有定义宽度,而是使用填充,因为我希望宽度由菜单项的宽度定义。我加粗当前选定的项目。
问题在于加粗会使单词略微变宽,这会导致其余元素略微向左或向右移动。有没有巧妙的方法可以防止这种情况发生?例如告诉填充忽略加粗导致的额外宽度?我的第一个想法是从“活动”元素的填充中减去几个像素,但这个量是变化的。
如果可能,我想避免对每个条目设置静态宽度,然后居中,而是使用我目前拥有的填充解决方案,以便将来更改条目变得简单。
我曾经遇到同样的问题,但通过一些妥协达到了类似的效果。我使用了text-shadow代替。
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>
li:hover {text-shadow: 1px 0 0 black;}
会导致文本字母之间的间距太小。为了再次改善这个问题,我们还需设置 li {letter-spacing: 1px;}
。 - Patrick Hammer使用 ::after 的最佳工作方案
HTML
<li title="EXAMPLE TEXT">
EXAMPLE TEXT
</li>
CSS
->CSS
li::after {
display: block;
content: attr(title);
font-weight: bold;
height: 1px;
color: transparent;
overflow: hidden;
visibility: hidden;
}
它通过使用title
属性提供的粗体文本宽度,添加一个不可见的伪元素。
text-shadow
解决方案在Mac上看起来不自然,并且没有充分利用Mac文本呈现所提供的所有美感。 :)
::after
块添加负margin-top
解决了这个问题。 - Rudolf Gröhlingtitle
属性,而且如果你没有 title
属性它甚至不能正常工作,这怎么能算是一个合适的解决方案呢? - codenamezeroheight: 0;
是安全的。 - Tigran最便携且视觉效果最佳的解决方案是使用text-shadow
(感谢Thorgeir's answer和atorscho's comment)与-webkit-text-stroke-width
(可用时。感谢Oliver's answer)相结合。
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; }
}
这会在每个字符的两侧使用当前字体颜色的微小“阴影”,并使用能够与字体渲染正确缩放的单位。如果有浏览器支持,那么该阴影就会减半,我们会增加用于绘制文本的笔画的宽度。这看起来会更加清晰,因为没有模糊半径的阴影是块状的,而在较高级别上笔画则是模糊的(请参见下面的演示)。
警告: 虽然
px
值支持小数,但当字体大小改变时(例如用户使用Ctrl++缩放视图),它们看起来可能不太好。请改用相对值。此答案使用
ex
单位的分数,因为它们随字体缩放而缩放。
在大多数浏览器默认情况下*,期望1ex
≈8px
,因此0.025ex
≈0.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
变化的可变字体允许全范围的权重,但大多数字体要么缺少粗体变体,要么只有一个。需要呈现粗体字体的系统将根据需要自行完成,但仅在一个权重上(详细信息和示例在此处)。某些非可变字体提供几个权重,例如 Roboto,如下面的演示所示。在演示中玩转滑块以查看粒度差异。
(不要被大型代码块吓到,这主要用于实现交互式滑块并比较回答此问题的所有方法。)
@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 绘图技巧
,它留下尴尬的额外空间,以便粗体文本可以展开而不会推动相邻的文本项(我将归属放在粗体文本之后,以便您可以看到它不会移动)。
0.5px
是更好的选择。 - codenamezerocurrentColor
替代 black
或任何特定颜色(尽管我还没有在许多浏览器上进行测试)。 - David Gilbertson我发现当你将字母间距调整1像素时,大多数字体的大小都是相同的。
a {
letter-spacing: 1px;
}
a:hover {
font-weight: bold;
letter-spacing: 0px;
}
虽然这会改变常规字体,使每个字母之间多出一个像素的间隔。但是对于菜单标题来说,因为它们通常很短,所以不会有问题。
0.98px
或更小的分数来微调间距。 - Reactgular1px
转换为相对值如0.025ex
或0.0125ex
,当字体缩放时它也无法工作。请参见我的答案以进行实时演示。 - Adam Katz对于一个更加更新的答案,你可以使用-webkit-text-stroke-width
:
.element {
font-weight: normal;
}
.element:hover {
-webkit-text-stroke-width: 1px;
-webkit-text-stroke-color: black;
}
这避免了任何伪元素(对于屏幕阅读器来说是个优点)和文本阴影(看起来很凌乱,仍然可能会产生轻微的“跳跃”效果),或设置任何固定宽度(可能不切实际)。
它还允许您将元素设置为粗体1像素以上(理论上,您可以使字体加粗至您喜欢的程度,并且也可以是创建没有粗体变体的字体的粗体版本的一种较差的锻炼方式,例如自定义字体 (编辑:可变字体取消了此建议)。尽管应该避免这样做,因为它可能会使一些字体看起来划痕和崎岖不平)
我确定这在Edge,Firefox,Chrome和Opera(发布时)以及Safari中都有效(编辑:@Lars Blumberg感谢您确认)。它在IE11或更早版本中不起作用。
还要注意,它使用了-webkit
前缀,因此这不是标准的,未来可能会停止支持,因此如果粗体真的很重要,最好避免使用此技术,除非它仅仅是美学上的需要。
text-shadow
解决方案与此混合以减少@MightyPork指出的模糊。最新的解决方案是使用可变字体等级,这些字体在浏览器支持方面更好,但需要自定义字体(在我的答案中了解更多)。 - Adam Katz它们仍然是比例字体(而不是等宽字体),但它们在不同的字体粗细之间占据相同的大小。无需使用CSS、JS或花哨的技巧即可保持大小不变,因为这已经融入字体中了。
以下是一个来自这篇优秀文章的示例,比较了比例字体IBM Plex Sans和等宽字体Recursive。
您可以尝试的免费等宽字体包括:
该文章中还提供了许多非免费选项。
.btn {
font-weight: 400;
}
.btn:hover {
font-weight: 500;
letter-spacing: -0.001527em;
}
通过一些测试和使用上面的公式,您可以找到适合您情况的letter-spacing
值,而且它应该适用于任何字体大小。
唯一需要注意的是不同的浏览器使用稍微不同的子像素计算,因此如果您想要达到这种强迫症级别的子像素完美精度,您需要重复测试并为每个浏览器设置不同的值。针对浏览器的CSS样式通常会受到反对,这是有道理的,但我认为这是唯一合理的选择。
我花了几个小时浏览这里的答案,但对于text-shadow
和-webkit-text-stroke-width
解决方案、content:attr
等的渲染质量不满意。
因此,这里提供了一个JS替代方案,当鼠标悬停或点击时显示干净的加粗和放大字体。垂直菜单版本在Codeply 这里,水平版本在这里。在两种情况下,注释掉对fixElementSize
的调用以查看差异:使用调用,菜单项定位保持坚如磐石。
这个函数完成了艰苦的工作(对于水平版本,请将“height”替换为“width”),但它需要box-sizing:border-box
和display: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';
}