2014年 @Jason Pamental 写了一篇博客详细介绍了Web排版的缩放。 @Richard Rutter 在 另一篇文章 中介绍如何更好的表达你的排版。比如在一个Banner区,如何让文本显示的各为大气,又适合你的Web排版,如下图所示:
自从2010年响应式设计概念提出一直以来,在我的印象中: 图片的响应式 和 文本排版的响应式 都是制约响应式应用的两大阻碍。虽然有很多热衷于响应式设计的设计师和开发人员都在致力于解决这方面的问题。但至目前为止,还未看到一套有关于图片响应式的文本排版响应式的最佳解决方案。
当然,你可能会说我说的比较片面,为什么呢?你肯定会说,对于图片的响应式不是有了 <picture> 标签和 srcset 属性了?这不是很好的解决方案吗?对于文本排版,可能会想到在不同的断点之下改变 root 元素 html 的 font-size 来做相应的处理。那么问题来了?这些真的是很好的解决方案?值得我们去思考一下。
那么今天我们来思考一下响应式的文本排版,也就是标题所说的: 如何精确控制响应式排版 。
简单的回忆一下响应式排版的使用场景,其实他的使用场景对于现代Web排版还是很常见的。比如@Richard Rutter在他的博文中所说的,怎么让Banner区域标题在不同的终端屏幕下显示得更为大器(文章开头的示意图)。特别是在移动端时代,都希望在面对不同的移动终端的时候,能让自己的排版更为合适,如下图所示:
为了能更好的做出响应式排版, @MikeRiethmuller 提出了精准响应式排版的概念。对于这个概念我也是第一次听说。至于什么是精准响应式排版,我们先把这个概念放一放,先来看一段代码的截图:
上图中蓝色框框标出来的两段代码:
@media screen and (min-width: 20rem) {
h3,.h3 {
font-size: calc (1.266rem + .511 * (100vw - 20rem) / 100);
}
}
这里面有几个很熟悉的东东, @media 、 rem 、 vw 和 calc() 。那么结合在一起,就有点让人摸不着头脑了。这些计算是怎么来的,它的原理是什么?难道这就是所谓精准响应式排版的出处吗?欲知其中之原委,看官请继续。
既然实现“精确控制响应式排版”跟下面这段代码
@media screen and (min-width: 20rem) {
h3,.h3 {
font-size: calc (1.266rem + .511 * (100vw - 20rem) / 100);
}
}
有着千丝万缕的关系,那么我们就有必要对代码做一个分析。在分析这段代码之前,有几个相关的概念需要先和大家一起聊聊。
@media 在CSS Media Queries模块 ,也是CSS条件属性( Conditional CSS )之一。它也是响应式设计中必不可缺的属性,可以给 @media 传递相关参数,告诉浏览器选择对应的CSS规则。简单的理解就是在不同的断点下,渲染出不同的效果。这里就不对其做详细的阐述,如果感兴趣的话,可以点击这里进行了解。
calc() 是 CSS Values and Units Module Level 3 属性 之一 。它是CSS自带的函数功能,其最大功能就是可以帮助你做一些数学计算,比如:
width: calc( 100% - 20px);
可以很方便的帮助我们计算出 width 的值。特别是在一些流体布局中,比如上面的代码,我们需要在 % 值中减去一定的 px 值时,它让我们变得轻松。不过在使用 calc() 时有一个细节需要特别的注意, 那就是在计算符的前后必须要有一个空格 。当然,通过 calc() 在CSS中做一些数值运算,很多同这担心会影响页面的性能,对于这方面,我也没做过相关测试,在网上找了一圈,并没有找到相关的文章介绍,如果你比较擅长这方面,欢迎做一些相关的测试,并且把你的测试结果与大家分享。那么有关于其详细的介绍,可以阅读早前整理的另外一篇博文《 CSS3的 calc() 使用 》。
rem 和 vw 都是 CSS中的单位 ,简单点讲他们都是CSS单位中的相对单位,可以根据一定的参考值做相应的变化。那么这也是我们这篇文章的重点之一。接下简单的阐述一下几个比较重要的单位: rem 、 vw 、 vh 、 vmin 和 vmax 。
rem 是相对于根元素 html 的 font-size 来计算。基于这个原理,在响应式排版中,通过 @media 在不同的断点之下修改 html 元素的 font-size ,然后对应使用 rem 单位的元素就会做相应的变化。例如下面的代码:
html {
font-size: 16px;
@media (min-width: 800px) {
font-size: 18px;
}
@media (min-width: 1200px) {
font-size: 20px;
}
}
假设 h1 、 h2 和 h3 的 font-size 分别是 50px 、 37px 和 28px ,那么其在默认情况下以及 800px 和 1200px 对应的 font-size 将如下表所示:
| @media 断点 | HTML (font-size) | h1(50px) | h2(37px) | h3(28px) |
|---|---|---|---|---|
| 默认 | 16px | 3.125rem | 2.3125rem | 1.75rem |
| 800px | 18px | 2.777rem | 2.055rem | 1.555rem |
| 1200px | 20px | 2.5rem | 1.85rem | 1.4rem |
根据上表的数据,不难表现,不管在什么样的断点下,元素 h1 、 h2 和 h3 最终的 font-size 都是一样的。并没有达到@Richard Rutter所描述的,在不同的屏幕(断点)具有不同的 font-size ,让你的文本在Banner区显得大气。言外之意,也没有达到精确响应式布局效果。
上面简单的回忆了 rem 在CSS中的简单原理,如果你想更进一步的了解 rem 的话可以阅读《 CSS3的REM设置字体大小 》一文或者点击这里做进一步深入的了解。
vw 、 vh 、 vmin 和 vmax 也是 CSS的相对单位 ,但它们又称之为视窗单位。它们的大小都是由视窗大小来决定的,不管哪个值, 1 就相当于视窗 width 或 height 的 1% 。具体描述如下:
vw :视窗宽度的百分比 vh :视窗高度的百分比 vmin :当前较小的 vw 和 vh vmax :当前较大的 vw 和 vh 在这种情况下,视窗,指的是浏览器屏幕。 1vw 就意味着 1% 的浏览器的宽度。 100vw 将意味着整个浏览器宽度。
视窗单位的好处在于当视窗大小改变时,他们会自动的重新计算其大小。当重新加载页面,调整页面大小或改变页面方向时就会发生此现象。
那么在响应式排版本中,如果 font-size 使用 vw 作为元素的单位,那么元素的 font-size 大小直接跟视窗大小有关系,言外之意就跟断点有直接关系。比如:
h1 {
font-size: 13vw;
}
其在不同的断点下的 font-size 就如下表所示:
| @media (断点) | h1(13vw) |
|---|---|
| 320px | 320 ÷ 100 × 13 = 41.6px (42px) |
| 768px | 768 ÷ 100 × 13 = 99.84px (100px) |
| 1024px | 1024 ÷ 100 × 13 = 133.12px (133px) |
| 1280px | 1280 ÷ 100 × 13 = 166.4px (166px) |
| 1920px | 1920 ÷ 100 × 13 = 249.6px (250px) |
使用 vw 做为单位值主要是跟视窗的 width 有关系,比如在移动设备,横屏和竖屏下,他们的值就不一样,因为横屏和竖屏的视窗大小是不一样的。如下图所示:
标题在横屏下明显的要比在竖屏下大得多。
vh 和 vw 非常的类似,只不过是根据视窗的高度来进行计算。这里就不再做累述。
虽然使用 vw 单位之后,元素的 font-size 会跟随视窗大小做相应的变化,相比而言,比前面的 rem 更具响应式,但还是并不完美。仔细观察一下,文本不管在什么终端下(比如iPhone),那么他的横屏或竖屏状态下,它的可视空间是一样的,如果我们让文本在可视空间具有一定的 font-size ,也就是说视觉效果更加一致,是不是更完美一些呢?那么这个时候 vmin 相对而言就要比 vw 完美一些。
再继续下面的内容之前,先简单的了解一下 vmin 或者说 vmax 。
vh 和 vm 总是与视口的高度和宽度有关,与之不同的, vmin 和 vmax 是与这次宽度和高度的最大值或最小值有关,取决于哪个更大和更小。例如,如果浏览器设置为 1100px 宽、 700px 高, 1vmin 会是 7px , 1vmax 为 11px 。然而,如果宽度设置为 800px ,高度设置为 1080px , 1vmin 将会等于 8px 而 1vmax 将会是 10.8px 。
设想你需要一个总是在屏幕上可见的元素。使用高度和宽度设置为低于 100 的 vmin 值将可以实现这个效果。例如,一个正方形的元素总是至少接触屏幕的两条边可能是这样定义的:
.box {
height: 100vmin;
width: 100vmin;
}
.box {
height: 100vmax;
width: 100vmax;
}
如此一来,在一个可控空间,相比 vw 单位,在排版中采用 vmin 会将更适合。简单来看看不同Viewport尺寸下 vw 和 vmin 对应的值( 13vmin ):
| Viewport | 13 vw | 13 vmin |
|---|---|---|
| 320 × 480 | 42px | 42px |
| 414 × 736 | 54px | 54px |
| 768 × 1024 | 100px | 100px |
| 1024 × 768 | 133px | 100px |
| 1280 × 720 | 166px | 94px |
| 1366 × 768 | 178px | 100px |
| 1440 × 900 | 187px | 117px |
| 1680 × 1050 | 218px | 137px |
| 1920 × 1080 | 250px | 140px |
| 2560 × 1440 | 333px | 187px |
来假设一下,在没有断点的情况之下,如果我们想从 400px 的 font-size: 16px 过渡到 800px 的 font-size: 24px; 。我们可以通过断点最小和最大字号来进行计算。这个时候就需要使用到前面介绍的 calc() 函数。将 calc() 和视窗单位结合在一起,这样一来可以让我们的排版更具灵活,这样也实现了在一个范围内的视窗下有具体的像素值。
@media screen and (min-width: 20rem) {
h3,.h3 {
font-size: calc (1.266rem + .511 * (100vw - 20rem) / 100);
}
}
将代码中的数学运算部分抽取出来,就类似下图所示:
上图就不做详细的阐述了,我想大家看图就应该知道其中的意思。
经过前面的大篇篇幅,对其中的一些属性的使用性质以及相关的用法有了一定的了解,接下来回到本文的正题, 如何精确控制响应式排版? 其实要实现这一个特性,说实话,也就是如何更好的控制Web页面中各元素的 font-size 大小,让其值在适合的空间有一个更适合的字号。接下来简单的分几个步骤。
calc() 限制字体缩放 如果你想设置一个确切的最小字号,那么可以通过 calc() 和 px 以及 vw 结合在一起。比如:
:root {
font-size: calc( 16px + 3vw);
}
上面的代码就是设置了根元素的默认 font-size 值为 16px + 3vw 。
注意:在一些浏览器使用视窗单位和 calc() 结合在一起还是会有一点的问题,所以为了安全,最好是在媒体查询中使用。
为了防止文本缩放的比例低于特定的阈值,我们只需要借助媒体查询。在特定的的断点之下采用。
:root {
font-size: 18px;
}
@media (min-width: 600px){
:root {
font-size: 3vw; /*3 × 600 ÷ 100 = 18px*/
}
}
calc() 精确控制字号大小 根据上述的表达式,我们就可以借助 calc() 控制一个精确的 font-size :
font-size: calc( 12px + (24 - 12) * ( (100vw - 400px) / ( 800 - 400) ));
这行代码实现了从 400px 的 font-size: 16px 过渡到 800px 的 font-size: 24px; 。为了能在响应式下更好的精确控制文本的 font-size 。我们只需要在媒体查询下控制 :root 的 font-size ,这就回到了文章前面看到的代码:
:root,html {
font-size: .875rem
}
@media screen and (min-width: 20rem) {
:root,html {
font-size:calc(.875rem + .25 * (100vw - 20rem) / 100)
}
}
@media screen and (min-width: 120rem) {
:root,html {
font-size:1.125rem
}
}
使用的时候只需这样:
body {
font-size: 1.125rem;
}
除了可以运用于文本之外,这种思路也可以运用于其他地方,比如说 line-hieght 、 width 之类。有关于这方面的示例可以 点击这里查阅 。
如果你坚持将文章看到这里,你也应该知道其中的原委。为了方便日后使用,我们可以借助于Sass这样的CSS预处理对其进行封装。比如封装一个 fluid-type 的混合宏。在这个混合宏,我们将传递几个参数:
$properties : CSS的属性,比如 width 、 line-height 和 font-size 等 $min-vw :视窗最小宽度 $max-vw :视窗最大宽度 $min-value :最小值 $max-value :最大小值 我们可以这样写 @mixin fluid-type :
@mixin fluid-type($properties, $min-vw, $max-vw, $min-value, $max-value) {
& {
@each $property in $properties {
#{$property}: $min-value;
}
@media screen and (min-width: $min-vw) {
@each $property in $properties {
#{$property}: calc(#{$min-value} + #{strip-units($max-value - $min-value)} * ((100vw - #{$min-vw}) / #{strip-units($max-vw - $min-vw)}));
}
}
@media screen and (min-width: $max-vw) {
@each $property in $properties {
#{$property}: $max-value;
}
}
}
}
假设 $min-vw ( $minScreen )的值为 20rem 、 $max-vw ( $maxScreen )的值为 50rem , $min-vaule ( $minFont )为 .8rem , $max-value ( $maxFont )为 2rem 。在 :root 中调用的时候:
// SCSS变量
$minScreen: 20rem; // $min-vw
$maxScreen: 50rem; // $max-vw
$minFont: .8rem; // $min-value
$maxFont: 2rem; // $max-value
:root {
@include fluid-type(font-size, $minScreen, $maxScreen, $minFont, $maxFont);
}
编译出来的CSS:
:root {
font-size: 0.8rem;
}
@media screen and (min-width: 20rem) {
:root {
font-size: calc(0.8rem + 1.2 * ((100vw - 20rem) / 30));
}
}
@media screen and (min-width: 50rem) {
:root {
font-size: 2rem;
}
}
除了Sass之外,还可以使用PostCSS插件 rucksack-css 。
这篇文章简单介绍了如何通过CSS的 calc() 函数以及CSS的一些相对单位,比如 rem ,特别是视窗单位 vw 或者 vmin 来实现精确的响应式排版。并且介绍了其中一些实现原理以据依据。如果你感兴趣的话,可以在自己的测试项目中实践一下。如果你想看一些别人写好的DEMO,那么你可以在Codepen上浏览 @MadeByMike 整理的 一些案例 。
如果你有类似或更具创意的Demo,也可以在Codepen上书写,并且将你的Demo地址在下面的评论中分享给我们。或者说你有更好的思路,也欢迎与我们一起分享。
calc()
常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。