响应式技巧指南
今天看到了一个网站:react.gg/ ,一个学习 React 的网站,当你打开它有点花里胡哨,但好像也没啥特别的。
不过有职业习惯的我,顺道打开 devtools 开了,发现了些门道,就是今天的主角—— fontsize clamp 属性。
clamp 也不是啥陌生人了,它配合 min()/max()
这 3 个数学函数在 2018 年底开始支持,至今五个年头了,比我从业时间都长。但是平时遇到这三个参数往往都是在响应式设计布局中,今天神了奇了头一次在 fontsize 中遇到。
接下来讲在 fontsize 中使用 clamp 的好处。
响应式字体的解决方案
现代浏览器的默认字体大小一般是 16px,如果不挑剔,16px 在大多数设备上显示都没问题,可能唯一的缺点就是对于使用超大显示屏的人不友好,比如屏幕尺寸 2700+ 的显示屏,16px 字体的字就会非常小,一般需要手动放大才能看清。
大屏也好解决直接加个媒体查询就好了:
@media(min-width: 2000px) {
.title {
font-size: 3rem;
}
}
作为网站维护者,友好的体验的还是必要的,以无码科技的官网:nocode.com/ 为例,它需要支持三端运行——手机 + iPad + PC,网页默认字体大小为 16px,网页是恒定不变的,但三端的窗口是不一样的,一定程度上会导致内容主题不突出,讲人话就是内容不直观看不清。
根据习惯,小的屏幕字体就应该小点,用来显示更多的内容,大的屏幕字体就应该大点,内容看得清楚。手机、iPad、PC 屏幕从小到大,字体也应该从小到大,不应该一律默认为 16px。
短平快解决
为了解决这个问题,以前大家的套路很简单,凡是遇到响应式,一律媒体查询解决,比如通过媒体查询设置根元素的 fontsize,然后用 rem 引用:
html {
font-size: 16px;
}
@media screen and (min-width: 320px) {
html {
font-size: calc(16px + 6 * ((100vw - 320px) / 680));
}
}
@media screen and (min-width: 1000px) {
html {
font-size: 22px;
}
}
有啥缺点呢,就是体验不够丝滑,因为屏幕大大小小太多了,比如大点手机屏幕直接比肩常规的 iPad,大点 iPad 直接和电脑一样大,至于超大显示器更多了。
想要丝滑还有另一种解决方案。
视口法
想要自动调节,那就是使用视口宽度单位 vw 作为字体的单位。比如:
h1 {
font-size: 5.9vw;
}
这种方法太过没节操,可以无限大,也可无限小,字体完全跟随着屏幕大小变化。
事实上,当屏幕小/大到一定程度,为了良好的体验,字体就不应该再改变了。
甚至在代码实践中,为了最佳显示效果会手动设置屏幕的最大宽度 Max-width 和最小宽度 min-width,比如携程移动端:
因为没有设置最小宽度,特小得屏幕上显示就崩溃了:
clamp 方法
在一定范围内设置字体大小,这简直就是为 clamp 定制的需求。CSS 的 clamp(minimum, preferred, maximum)
函数有三个参数:最小值、首选值(推荐值)和最大值,它将限制一个值的取值范围,确保它不会小于最小值或大于最大值,并且在最小值和最大值之间以首选值为中心进行变化。
比如我们常常会这样设置宽度,让其在 300px ~ 600px 之间变化。
width: clamp(300px, 50%, 600px);
比如设置网页常规字体大小如下:
.title {
font-size: clamp(16px, 5vw, 50px);
}
这会设置浏览器窗口在 (16 * 20 = )320px
到 (50 * 20 = )1000px
的范围内字体从 16px ~ 50px 进行变化。
可能看到这里你就要关闭文章了,但事情还没结束。
首先,如何工程化的应用 clamp 就是一个难题,比如下面截图的一篇博客:
标题和内容肯定要设置不同的 fontsize,更何况 HTML 中有六个标题 p 和 a 等标签字体大小都是不同的。
其次,大家看我们确定 clamp 最大值和最小值的方式,完全人脑计算很容易出错,比如:
font-size: clamp(1.2rem, 5vw, 2.1rem);
要想知道浏览器窗口在多大范围内触发 fontsize 的变化范围,不免颇费周折的计算一番,这就导致我们想要给固定窗口大小的浏览器指定变化范围很难。
最后,clamp 的推荐值 5vw 这种书写方式,有一个很大的体验问题,会遇到浏览器放大缩小字体无效的情况:
给出一个标题和一旦文字,标题和文字都没有设置字体的大小,全部采用默认的样式:
我们使用浏览器的缩放功能把页面放大 2 倍,字体也被放大两倍,看下对比:
等比缩放没有任何问题。
现在调整标题 h1 的 fontsize 为 font-size: clamp(16px, 5vw, 50px);
看下对比效果:
缩放两倍的 h1 好像变化不明显,打开控制台我们就明白了,fontsize 被改变了:
测量下视口大小就明白了,宽 649px 小于 1000px,原因很清楚了,放大动作导致了视口变小,clamp 正常运作了
如何计算 clamp 的推荐值
确定 body 作为基准,然后定义标题 h1 ~ h6 的最大/小值。
Font Element | h6 | body | h5 | h4 | h3 | h2 | h1 |
---|---|---|---|---|---|---|---|
Minimum | 0.90 | 1.00 | 1.20 | 1.40 | 1.60 | 1.80 | 2.00 |
Maximum | 1.00 | 1.10 | 1.45 | 1.80 | 2.10 | 2.45 | 2.75 |
上表用 Excel 生成折线图更加直观展示:
现在给出几个前提条件:
- 浏览器默认的 fontsize 为 16px。
- 最小屏幕尺寸为 380px。
- 最大屏幕尺寸为 1800px。
现在以 h1 为例,fontsize 会在 2 到 2.75 之间变化,待会我们把它作为折线图的 y 轴。
同时,最小值 2rem 在 380px 屏幕下出现,此时页面有 (380px / 16px = 23.75) 个 rem,最大值 2.75 在 1800px 屏幕下出现,此时页面有(1800px / 16px = 112.5)个 rem。
我们把计算结果放到图上,会得到一个一次函数 y = kx + b
知道两点坐标带入求解,就能得到方程 y = 0.0085x + 1.7993。
因为视口 vw 是以百分比来描述的,所以我们需要将斜率乘以 100 来实现我们的 clamp 函数:
y = 0.85vw + 1.7993rem
此时的 h1 为:
h1 {
font-size: clamp(2rem, 1.799rem + 0.85vw, 2.75rem);
}
1.799rem + 0.85vw
完美解决了上面提到直接写 4w 引发的三个问题。
现在得到公式了,就可以写个简单的小工具,指定浏览器大小,clamp 的最大值和最小值,推荐值就能自己计算:min-max-calculator.9elements.com/
工程化设计
借助 min-max-calculator.9elements.com/ 工具我们就可以自己设计网站的 fontsize 大小和空间布局大小。
比如下面这份 CSS 清单就是 9elements.com/blog/ 使用的模板:
:root {
--space-3xs: clamp(0.25rem, 0.23rem + 0.08vw, 0.31rem);
--space-2xs: clamp(0.5rem, 0.48rem + 0.08vw, 0.56rem);
--space-xs: clamp(0.75rem, 0.72rem + 0.16vw, 0.88rem);
--space-s: clamp(1rem, 0.97rem + 0.16vw, 1.13rem);
--space-m: clamp(1.5rem, 1.45rem + 0.25vw, 1.69rem);
--space-l: clamp(2rem, 1.93rem + 0.33vw, 2.25rem);
--space-xl: clamp(3rem, 2.9rem + 0.49vw, 3.38rem);
--space-2xl: clamp(4rem, 3.87rem + 0.66vw, 4.5rem);
--space-3xl: clamp(7rem, 6.77rem + 1.15vw, 7.88rem);
--space-4xl: clamp(12rem, 11.61rem + 1.97vw, 13.5rem);
--space-3xs-2xs: clamp(0.25rem, 0.17rem + 0.41vw, 0.56rem);
--space-2xs-xs: clamp(0.5rem, 0.4rem + 0.49vw, 0.88rem);
--space-xs-s: clamp(0.75rem, 0.65rem + 0.49vw, 1.13rem);
--space-s-m: clamp(1rem, 0.82rem + 0.9vw, 1.69rem);
--space-m-l: clamp(1.5rem, 1.3rem + 0.99vw, 2.25rem);
--space-l-xl: clamp(2rem, 1.64rem + 1.81vw, 3.38rem);
--space-xl-2xl: clamp(3rem, 2.61rem + 1.97vw, 4.5rem);
--space-2xl-3xl: clamp(4rem, 2.98rem + 5.1vw, 7.88rem);
--space-3xl-4xl: clamp(7rem, 5.29rem + 8.55vw, 13.5rem);
--text-step--2: 0.875rem;
--text-step--1: clamp(0.781rem, 0.757rem + 0.12vw, 0.875rem);
--text-step-0: clamp(1rem, 0.967rem + 0.16vw, 1.125rem);
--text-step-1: clamp(1.125rem, 1.092rem + 0.16vw, 1.25rem);
--text-step-2: clamp(1.25rem, 1.184rem + 0.33vw, 1.5rem);
--text-step-3: clamp(1.438rem, 1.257rem + 0.9vw, 2.125rem);
--text-step-4: clamp(2rem, 1.638rem + 1.81vw, 3.375rem);
--text-step-5: clamp(2.5rem, 1.513rem + 4.93vw, 6.25rem);
--text-step-6: clamp(2.938rem, 1.079rem + 9.29vw, 9.5rem);
}
使用起来就非常的友好:
对于空间尺寸 9elements 使用的是原子化 CSS 类名的命名规范,对于 fontsize 采用的则是传统 HTML 六个标题和正文按照字号大小排序,当然你也可以重写为符合原子 CSS 的变量:
text-xs
text-sm
text-base
text-lg
text-xl
text-2xl
text-3xl
text-4xl
text-5xl
text-6xl
text-7xl
text-8xl
text-9xl
编程随想
我不知道你是否熟悉这个网络 ID,21 年就消失了,最近出现了,却不是一个好的结局,因为发现社会中存在一些 bug,提了些 issues 开了几个 PR,直接 emoji 了。
希望他和他的家人都健健康康,都好好的活着,一起见证没有谎言修饰的明天。