font-weight从600改成500为何跳崖式变细?微软雅黑字重映射规则与-webkit-text-stroke描边微调实战

前言:为什么在Windows上把font-weight从600改成500,字体会断崖式变细?根源是微软雅黑只有Light、Regular、Bold三个字重文件,浏览器按字重映射规则把600以上归到Bold、500以下归到Regular。本文给出-webkit-text-stroke描边微调等三种方案实现无级平滑变换字体厚度(font-weight),精准还原设计稿字重。

我把字体粗细从600改成500,它跳崖了

前两天在做一个开业海报,六张商品图并排那种。

设计师把稿子发过来,我照着撸代码。标题、副标题、价格、商品名,一个一个往HTML里填。填到商品名的时候,设计师在旁边看了一眼,说这个标题太粗了,能不能稍微细一点点。

我说行,这个简单。

打开CSS,找到 .product-name,把 font-weight: 600 改成 500

保存,刷新。

然后我就愣住了。

标题确实变细了。但不是「稍微细一点点」,是突然塌下去了,像从二楼直接跳到一楼,中间的楼梯没了。笔画一下子单薄了好几个档次,跟设计稿完全对不上。

我当时就???

不是,我明明只改了一个数字,从600到500,差了100,怎么视觉效果像差了一整个字重?

600到500,不是渐变,是跳崖
600到500,不是渐变,是跳崖

带着这个疑问我开始翻资料,翻完才发现,这事儿比我想的要有意思得多。

先说一个很多朋友可能不知道的前提。我项目里写的字体声明是这样的,font-family: "PingFang SC", "Microsoft YaHei", sans-serif。苹方在前,微软雅黑兜底。在我的Mac上,苹方有完整的字重系列,从细到粗随便挑,所以600改500,确实能看到一个平滑的变化。

但这个海报最终要跑在Windows上。Windows没有苹方,浏览器老老实实回退到了微软雅黑。

问题就出在这。

微软雅黑这个字体,它装在Windows系统里的字重文件,只有三个。一个Light细体,对应CSS里的300。一个Regular常规体,对应400。一个Bold粗体,对应700。就这三个,没了。

你写500,系统里没有500对应的字体文件。你写600,也没有。那浏览器怎么办呢?它有一套映射规则,写在CSS规范里的。font-weight: 600及以上,600到900,全部映射到Bold,也就是700。font-weight: 500及以下,400到500,全部映射到Regular,也就是400。

所以你在Windows上写500和写400,渲染出来一模一样。写600和写700,也一模一样。

从600改到500,你以为降了一档,实际是从Bold直接跌到Regular。中间没有过渡,没有平滑,就是一刀切下去。

难怪跳崖。

微软雅黑,就这三档
微软雅黑,就这三档

搞清楚原因之后,我反而兴奋起来了。因为这事儿太典型了,你以为是连续的,其实是二值的。你以为你有一把精密的刻度尺,其实只有两个刻度。

那怎么解决呢,我试了三种方案,一个一个说。

第一种,也是最推荐的一种,用 -webkit-text-stroke 做描边微调。

思路是这样的。既然字重只有400和700两档,那我先选一个基准,然后在基准上用描边来「微调」粗细。比如以Regular 400为底,给它加一个同色的内描边,笔画就会变粗一点。-webkit-text-stroke: 1.8px #101010,笔画增厚大概1.8像素,刚好落在400和700之间,你要多细就调多小,0.3px、0.5px、0.8px都行,任意小数,平滑得很。

反过来也行。以Bold为底,加一个透明或背景色的描边,把粗体「削薄」一点,也能往中间靠。

这个方案的好处是,不需要引入任何外部字体文件,纯CSS搞定,而且精度极高,0.1px级别都能调。坏处嘛,说实话我也还在摸索,不同字号下描边的粗细感观不一样,大字号需要更粗的描边,小字号反而要收着来,需要反复试几次才能找到那个「刚刚好」的值。一开始可能会有点笨拙,调起来比直接改数字费劲,但一旦找到感觉,比什么都精确。

我最后就是用了这个方案。商品名以500为基准,叠了1.8px的描边。副标题同样500基准,叠1.0px。一个偏重一个偏轻,层次感出来了,跟设计稿也对上了。

描边1.8px,刚好卡在400和700中间
描边1.8px,刚好卡在400和700中间

第二种方案,引入真正支持500字重的Web字体。

微软雅黑没有500,那就找一个有的。开源的思源黑体,也叫Noto Sans SC,它原生设计了Medium 500这个字重,是专门画的,不是合成的。引一行Google Fonts的链接,font-weight: 500 就能真正渲染出Medium的效果。

听着很完美对吧。但有个现实问题,如果你的海报要跑在内网或者离线环境,Google Fonts用不了,你得自己把woff2文件下下来放本地。而且一个中文字体文件动辄好几兆,加载慢,首屏会闪一下。对于我这种本地跑的海报HTML来说,有点杀鸡用牛刀了。

不过如果你做的是正经的Web项目,对字体粗细要求高,又不在乎加载成本,这个方案是最干净的。一个link标签解决所有问题,不用描边这种hack。

第三种方案比较玄学,用 letter-spacing 配合 -webkit-font-smoothing: antialiased 来减少像素挤压感。

这个不算真正改变字重,更像是「让字看起来没那么糙」。字间距稍微拉开一点,抗锯齿打开,视觉上会感觉字「清爽」了一些,有时候能达到类似「变细」的错觉。

我自己试下来,效果有限,聊胜于无吧。如果你对粗细的要求没那么精确,只是觉得字「有点糊」,可以试试。但如果你要的是设计稿那种精确的Medium效果,这个搞不定。

玄学方案,效果有限
玄学方案,效果有限

三种方案聊完,说真的,我自己最后选的是第一种。原因很简单,不依赖外部资源,精度够高,而且我那个场景就是本地HTML海报,没有加载负担。

但我也得坦诚说一句,-webkit-text-stroke 这个属性,名字里带着 -webkit-,你就知道它不是什么标准属性。虽然现在主流浏览器都支持了,Chrome、Safari、Edge都没问题,但Firefox的表现偶尔会有点微妙。如果你的项目要兼容老版本Firefox,最好测一下。这个坑我也踩过,调了半天发现是浏览器渲染差异,给我一下子整不会了。

顺着这个事儿再聊聊,我有时候觉得,这种「你以为有但其实没有」的精度,在数字世界里到处都是。

你写CSS的时候,font-weight 接受100到900,九个值,看着像一把连续的刻度尺。但实际渲染的时候,系统字体只给你两三个刻度,剩下的全是映射。你以为你在做精细控制,其实你在两个桶之间选一个。

这事儿让我想起一个词,叫「量化」。信号处理里的概念,把一个连续的模拟信号切成离散的数字台阶。黑胶唱片是模拟的,理论上沟槽里藏着无限的信息密度。MP3是数字的,采样率再高也是一段一段的。从模拟到数字,一定会丢东西,丢的就是那些「中间态」。

字体粗细也一样。设计师在Figma里拖一个滑块,从Regular到Bold之间可以无限细分,那是矢量世界的精度。但落到系统字体上,就变成了两个台阶。中间那些细腻的过渡,全被量化掉了。

-webkit-text-stroke 这种hack,说到底就是在用描边把那些被量化掉的中间态「补」回来。不是真正的Medium字重,是一个穿着Medium外衣的Regular。但只要视觉上骗过了你的眼睛,它就是Medium。

数字世界里,眼见为实。

连续的世界,离散的台阶
连续的世界,离散的台阶

回到我那个海报。

商品名1.8px描边,副标题1.0px描边,弹窗预览区按比例缩到0.35px和0.2px。保存,刷新,打开Windows看了一眼。

稳了。

标题不笨重也不发虚,刚好卡在Regular和Bold中间那个设计稿想要的位置。副标题比主标题轻一个层次,呼吸感出来了。弹窗里实时预览跟大卡视觉一致,不会再出现「预览一个样导出另一个样」的尴尬。

设计师看了一眼,说,嗯,这个可以。

我当时心里那个舒坦。。。

一个 -webkit-text-stroke,一行CSS,解决了一个困扰我半小时的跳崖问题。这种小技巧不值钱,但攒多了,就是你跟那些「改了没反应」的诡异bug搏斗时的弹药。

我不知道对大家有没有用,但这个事儿我踩过坑,就把过程原原本本掏出来了。如果你也在Windows上做中文字体排版,遇到字重怎么调都不对劲的情况,试试描边微调,也许能省你半天功夫。

毕竟,磨平一些信息差,就是我能做的全部了。


/ 注意,本项目仅在 SyncMein 的500人小群分享。
/ 谢谢你看完了我的文章,我们下次再见吧。
/ 作者:明察
/ 投稿或爆料,请联系邮箱:mingcha@gqmg.com

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容