• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

实现精准的流体排版原理

CSS3 web前端中文站 3年前 (2017-05-04) 795次浏览 已收录 0个评论

typography 排版

流体排版这一词似乎看上去有点陌生,在英文中常把他称之为Fluid Typography,当然也有很多朋友称之为流体字号(Fluid Size)。大概的意思就是 Web 排版中的font-size会根据浏览器窗口的大小自动改为。比如下图所示的一个效果:

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

实现精准的流体排版原理

看到上图的效果,大家首先可能会想到的是 CSS 中的 Viewport 单位vw或者vh之类,当然也可能会认为是通过媒体查询来改变元素font-size来实现的。事实上,他们都能实现类似的效果,但问题是我们想要精确的实现流体排版(根据视窗大小变化精确改变font-size的值),那并不是件容易的事情。那问题来了,我们有没有方法可以实现所谓的精准流体排版呢?答案是肯定的,接下来我们就要来探讨这方面的实现思路、细节以及使用到的一些数学公式。

实现思路

精准流体排版最核心的就是浏览器视窗大小改变时,font-size能够根据视窗的大小做到精准的变化。当用户收缩和拉大浏览器窗口时,其大小有一个变化,在 CSS 中,咱们通过把每一个大小点称为断点,断点也是媒体查询中一个重要的概念。除此之外,如果我们用 Viewport 单位来描述的话,视窗大小始终是100vw。如果font-size设置为2vw,那么其大小就是浏览器窗口宽度的2%,当窗口拉到1000px时,这个时候font-size对应的是20px

原理是不是很简单,而且其中还涉及到一些数学计算,CSS 中动态计算的话,可以依赖calc()函数来进行计算,详细的使用方式可以点击这里。

其实有关于这方面的介绍,在早期分享的文章中也或多或少的提到过:

  • Web 排版的缩放
  • 如何精确控制响应式排版
  • 基于视窗单位的排版

为了方便大家使用,在 Sassmagic 仓库中,使用 SCSS 声明了一个混合宏:

/// Fluid vertical rhythm and Fluid Modular scale /// @param {string} $properties - CSS 属性 /// @param {string} $min-vw - 视窗最小宽度(viewport min-width) /// @param {string} $max-vw - 视窗最大宽度(viewport max-width) /// @param {string} $min-value - 最小值 /// @param {string} $max-value - 最大值 @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;             }         }     } } 

只需要这样调用:

$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); } 

就可以编译出:

: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;     } } 

既然前面都有多篇文章介绍过了,为何还需要花时间来整理这篇文章呢?正如文章开头所说,我们今天主要介绍一些细节和原理以及一些数学知识。在继续阅读下面的内容之前,需要特别感谢@Jake Wilson分享的博文:《CSS Poly Fluid Sizing using calc(), vw, breakpoints and linear equations》。这篇文章介绍了精准流体排版的一些细节以及用到的相关数学知识。接下来的文章中,将会直接使用@Jake Wilson 文章中使用到的公式。

计算的演变过程与细节

假设页面中有一个h1的标题元素,希望在不同的断点之下有不一样的font-size,这样可以让我们阅读体验更友好。比如:

  • 在小屏幕下(Small:576px)标题h1font-size22px
  • 在中间屏幕下(Medium:768px)标题h1font-size24px
  • 在大屏幕下(Large:992px)标题h1font-size34px

前面也提到过了,改变font-size我们有多种方式。首先来看 CSS 媒体查询的实现方式:

h1 {     font-size: 22px; } @media (min-width:576px) {     h1 {         font-size: 22px;     } } @media (min-width:768px) {     h1 {         font-size: 24px;     } } @media (min-width:992px) {     h1 {         font-size: 34px;     } } 

是不是很简单,大家感兴趣的话,可以把这段代码复制到你的项目中,你就能看到效果。如果为了效果看上去更佳,你可以在h1中添加一个过渡效果:

h1 {     font-size: 22px;     transition: font-size .2s; } 

虽然你在不同的断点下,借助媒体查询的特性,能轻易的改变font-size值,从而得到你所需要的效果。但如果你够仔细的话,他的改变都是一下跳到断点对应的值(特别是没有添加transition属性的时候)。另外他只适应三个断点内,如果你需要更多的屏幕断点效果时,需要不断的添加媒体查询的条件以及对应的改变font-size值。

是不是感觉有点蛋疼,而且难维护。此时可能很多同学会立马想到 CSS 新特性:Viewport 单位(vw。那来看看vw应用,比如给h1标题设置font-size的值为2vw。根据vw的一些原理,我们可以计算出对应的值(还是拿前面所说的三个断点为例吧):

  • 576px2vw对应的是 576 * 2% = 11.52,也就是说这个时候font-size的值为11.52px
  • 768px2vw对应的是 768 * 2% = 15.36,也就是说这个时候font-size的值为15.36px
  • 992px2vw对应的是 992 * 2% = 19.84,也就是说这个时候font-size的值为19.84px

从计算的结果上来看,这几个值并不是设计师所需的22px24px34px。这个时候我们可以进行反推,在不同的断点下它其实都是100vw,那么每1vw在不同断点下的值是:

  • 576px 对应的是576 / 100
  • 768px 对应的是768 / 100
  • 992px 对应的是992 / 100

接着继续推算出不同断点下,22px24px34px所对应的vw值:

  • 22px对应567px的值:22 / 576 * 100% = 3.82%,也就是3.82vw
  • 24px对应768px的值:24 / 768 * 100% = 3.13%,也就是3.13vw
  • 34px对应992px的值:34 / 992 * 100% = 3.43%,也就是3.43vw

这个时候,它们的值是对应上了,但依旧是离不开媒体查询,font-size在过渡的时候仍将跳跃。而且还会有一个奇怪的副作用:

断点767px时,3.82%对应的视窗宽度是29.33759999px。用户把浏览器宽度减小1px,这个时候font-size就立马跳到24px。这将是让人感到非常的奇怪。

那么我们的核心问题就是这个了,如何解决这个现象?

如果我们把这些数据做一个简单的图表统计,不难发现,屏幕宽度越大,元素对应的font-size值也就越大,如下图所示:

实现精准的流体排版原理

如果根据这个趋势图,h1可以得到所有分辨率尺寸下最接近匹配设计师所需要的font-size值。这里有一个数学公式,直线方程中的斜截式

y = mx + b 

其中参数对应的意义:

  • m直线的斜率(Slope)
  • by轴截距(y-intercept)
  • x是当前视窗宽度(Viewport Width)
  • yfont-size的值

这里关键是怎么来决定斜率(Slope)和截距(y-intercept)。决定这两个参数有多种方法,最常见的方法是最小二乘法:

实现精准的流体排版原理

看到这里,不是觉得数学对于一位程序员是有多么的重要(虽然偶还不是一位真正的程序员,但在制作一些 CSS 的动画以及 Canvas 的运用中,体会到了数学公式是多么的重要)。继续我们今天要聊的话题,既然知道如何计算出自己所需参数,那么怎么将这些运用到我们的 Web 开发中来呢?对于 CSS 而言,要具备计算能力,目前也仅有calc()函数可以帮助我们实现。

既然如此,我们就把y = mx + b转到我们 CSS 代码当中来:

h1 {     font-size: calc( {slope} * 100vw + {y-intercept}px); } 

要得到想要的结果还是斜率(slope)和截距(y-intercept)。因为我们的视窗宽度是100vw。而1vw单位就是视窗宽度的1/100。如果我们把斜率做多次的计算,比如100次,那么每一次对应的也就是1vw

这只是人肉的计算。那么有没有什么方式可以帮我们自动计算呢?这里我们可以采用 CSS 处理器来完成,比如 Sass:

/// leastSquaresFit /// Calculate the least square fit linear regression of provided values /// @param {map} $map - A Sass map of viewport width and size value combinations /// @return Linear equation as a calc() function /// @example ///   font-size: leastSquaresFit((576: 24, 768: 24, 992: 34)); /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function leastSquaresFit($map) {     // Get the number of provided breakpoints     $length: length(map-keys($map));      // Error if the number of breakpoints is < 2     @if($length < 2) {         @error "leastSquaresFit() $map must be at least 2 values"     }      // Calculate the Means     $resTotal: 0;     $valueTotal: 0;     @each $res, $value in $map {         $resTotal: $resTotal + $res;         $valueTotal: $valueTotal + $value;     }      $resMean: $resTotal / $length;     $valueMean: $valueTotal / $length;      // Calculate some other stuff     $multipliedDiff: 0;     $squaredDiff: 0;      @each $res, $value in $map {         // Differences from means         $resDiff: $res - $resMean;         $valueDiff: $value - $valueMean;          // Sum of multiplied differences         $multipliedDiff: $multipliedDiff + ($resDiff * $valueDiff);          // Sum of squared resolution differences         $squaredDiff: $squaredDiff + ($resDiff * $resDiff);     }      // Calculate the Slope     $m: $multipliedDiff / $squaredDiff;      // Calculate the Y-Intercept     $b: $valueMean - ($m * $resMean);      // Return the CSS calc equation     @return calc( #{$m * 100}vw + #{$b}px); } 

这样写,真的有效吗?打开这个 DEMO,然后调整你的浏览器大小,你就可以看到变化了。而且字体大小非常接近最初的设计要求。

实现精准的流体排版原理

现在虽然font-size能随着视窗变化非常接近设计师要的,但如果你追求完美的话,你可能还是不太会接受。这是因为一个线性趋势线是一个特定的font-size与特定的视窗宽度接近。这是继承的线性回归。在你的结果中总是会有一些错误。这个时候就需要一个权衡,你要不要一个准确性。

这个时候你可能会追求更好。那我们可以做到?

前面采用的是直线趋势线,使用的是最小二乘法。接下来我们再一起来看看多项式最小二乘法。就像一个多项式回归趋势线,可能看起来像这样:

实现精准的流体排版原理

他对应也有一个数学公式,只是变得更为复杂:

实现精准的流体排版原理

简单点说:想要越精准的曲线,需要更复杂的方程。但是非常的不幸,在 CSS 中我们使用calc()函数并不能完成这样复杂的方程式计算。具体来说,没有指数运算:

font-size: calc(3vw * 3vw); /* This doesn't work in CSS */ 

那又来了一个新问题,calc()不支持这种类型的非线性数学计算,那我们能怎么做?

我们来考虑一下断点加多元线性方程的方式来弥补这方面的欠缺。如果我们只计算每一对断点之间一条线,趋势图看起来像这样:

实现精准的流体排版原理

在这个例子中我们将计算22px24px之间的直线,然后另一个是24px34px之间的直线。用 Sass 看起来像这样:

h1 {     @media (min-width:576px) {         font-size: calc(???);     }     @media (min-width:768px) {         font-size: calc(???);     } } 

还记得我们前面介绍的方程式?

y = mx + b 

现在我们要说的是两个点,那我们的方程式就变成:

实现精准的流体排版原理

同样使用 Sass 的函数来完成上面的公式转换:

/// linear-interpolation /// Calculate the definition of a line between two points /// @param $map - A SASS map of viewport widths and size value pairs /// @returns A linear equation as a calc() function /// @example ///   font-size: linear-interpolation((320px: 18px, 768px: 26px)); /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function linear-interpolation($map) {     $keys: map-keys($map);     @if (length($keys) != 2) {         @error "linear-interpolation() $map must be exactly 2 values";     }      // The slope     $m: (map-get($map, nth($keys, 2)) - map-get($map, nth($keys, 1)))/(nth($keys, 2) - nth($keys,1));      // The y-intercept     $b: map-get($map, nth($keys, 1)) - $m * nth($keys, 1);      // Determine if the sign should be positive or negative     $sign: "+";     @if ($b < 0) {         $sign: "-";         $b: abs($b);     }      @return calc(#{$m*100}vw #{$sign} #{$b}); } 

在调用的时候可以这样使用:

h1 {     // Minimum font-size     font-size: 22px;      // Font-size between 576 - 768     @media (min-width:576px) {         $map: (576px: 22px, 768px: 24px);         font-size: linearInterpolation($map);     }      // Font-size between 768 - 992     @media (min-width:768px) {         $map: (768px: 24px, 992px: 34px);         font-size: linearInterpolation($map);     }      // Maximum font-size     @media (min-width:992px) {         font-size: 34px;     } } 

编译出来的 CSS:

h1 {     font-size: 22px; } @media (min-width: 576px) {     h1 {         font-size: calc(1.04166667vw + 16px);     } } @media (min-width: 768px) {     h1 {         font-size: calc(4.46428571vw - 10.28571429px);     } } @media (min-width: 992px) {     h1 {         font-size: 34px;     } } 

为了 Sass 能更好的高效工作,对前面的再进行封装一下,比如我们把其封装成一个poly-fluid-sizing()函数:

/// poly-fluid-sizing /// Generate linear interpolated size values through multiple break points /// @param $property - A string CSS property name /// @param $map - A SASS map of viewport unit and size value pairs /// @requires function linear-interpolation /// @requires function map-sort /// @example ///   @include poly-fluid-sizing('font-size', (576px: 22px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <jake.e.wilson@gmail.com>  @mixin poly-fluid-sizing($property, $map) {     // Get the number of provided breakpoints     $length: length(map-keys($map));      // Error if the number of breakpoints is < 2     @if ($length < 2) {         @error "poly-fluid-sizing() $map requires at least values"     }      // Sort the map by viewport width (key)     $map: map-sort($map);     $keys: map-keys($map);      // Minimum size     #{$property}: map-get($map, nth($keys,1));      // Interpolated size through breakpoints     @for $i from 1 through ($length - 1) {         @media (min-width:nth($keys,$i)) {             #{$property}: linear-interpolation((nth($keys,$i): map-get($map, nth($keys,$i)), nth($keys,($i+1)): map-get($map, nth($keys,($i + 1)))));         }     }      // Maxmimum size     @media (min-width:nth($keys,$length)) {         #{$property}: map-get($map, nth($keys,$length));     } } 

poly-fluid-sizing()函数中还依赖linear-interpolation()map-sort()list-sort()list-remove()几个函数:

linear-interpolation()函数

/// linear-interpolation /// Calculate the definition of a line between two points /// @param $map - A SASS map of viewport widths and size value pairs /// @returns A linear equation as a calc() function /// @example ///   font-size: linear-interpolation((320px: 18px, 768px: 26px)); /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function linear-interpolation($map) {     $keys: map-keys($map);      @if (length($keys) != 2) {         @error "linear-interpolation() $map must be exactly 2 values";     }      // The slope     $m: (map-get($map, nth($keys, 2)) - map-get($map, nth($keys, 1)))/(nth($keys, 2) - nth($keys,1));      // The y-intercept     $b: map-get($map, nth($keys, 1)) - $m * nth($keys, 1);      // Determine if the sign should be positive or negative     $sign: "+";     @if ($b < 0) {         $sign: "-";         $b: abs($b);     }      @return calc(#{$m*100}vw #{$sign} #{$b}); } 

map-sort()函数

/// map-sort /// Sort map by keys /// @param $map - A SASS map /// @returns A SASS map sorted by keys /// @requires function list-sort /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function map-sort($map) {     $keys: list-sort(map-keys($map));     $sortedMap: ();      @each $key in $keys {         $sortedMap: map-merge($sortedMap, ($key: map-get($map, $key)));     }      @return $sortedMap; } 

list-sort()函数

/// list-sort /// Sort a SASS list /// @param $list - A SASS list /// @returns A sorted SASS list /// @requires function list-remove /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function list-sort($list) {     $sortedlist: ();      @while length($list) > 0 {         $value: nth($list,1);          @each $item in $list {             @if $item < $value {                 $value: $item;             }         }          $sortedlist: append($sortedlist, $value, 'space');         $list: list-remove($list, index($list, $value));     }      @return $sortedlist; } 

list-remove()函数

/// list-remove /// Remove an item from a list /// @param $list - A SASS list /// @param $index - The list index to remove /// @returns A SASS list /// @author Jake Wilson <jake.e.wilson@gmail.com>  @function list-remove($list, $index) {     $newList: ();      @for $i from 1 through length($list) {         @if $i != $index {             $newList: append($newList, nth($list,$i), 'space');         }     }      @return $newList; } 

显然这种方法要强大的多,它不仅仅适用于font-size,它适用于任何带有单位或长度属性,比如marginpadding等。在实际使用当中,你可以使用poly-fluid-sizing()函数,当然你也可以使用前面最早提到的leastSquaresFit()函数。这里有一个poly-fluid-sizing()的使用示例。感兴趣的可以看看。

实现精准的流体排版原理

其他类似方案

@eduardoboucas 提供的responsive-font()混合宏:

/// Viewport sized typography with minimum and maximum values /// /// @author Eduardo Boucas (@eduardoboucas) /// /// @param {Number}   $responsive  - Viewport-based size /// @param {Number}   $min         - Minimum font size (px) /// @param {Number}   $max         - Maximum font size (px) ///                                  (optional) /// @param {Number}   $fallback    - Fallback for viewport- ///                                  based units (optional) /// /// @example scss - 5vw font size (with 50px fallback),  ///                 minumum of 35px and maximum of 150px ///  @include responsive-font(5vw, 35px, 150px, 50px); ///  @mixin responsive-font($responsive, $min, $max: false, $fallback: false) {     $responsive-unitless: $responsive / ($responsive - $responsive + 1);     $dimension: if(unit($responsive) == 'vh', 'height', 'width');     $min-breakpoint: $min / $responsive-unitless * 100;      @media (max-#{$dimension}: #{$min-breakpoint}) {         font-size: $min;     }      @if $max {         $max-breakpoint: $max / $responsive-unitless * 100;          @media (min-#{$dimension}: #{$max-breakpoint}) {             font-size: $max;         }     }      @if $fallback {         font-size: $fallback;     }      font-size: $responsive; } 

总结

文章介绍了如何实现精准的流式排版。其中原理非常的简单,通过 CSS 的 Viewport 单位和calc()配合一些数学公式,较为精准的实现随着视窗改变,能较为精准的改变font-size的大小,甚至只要是带有长度单位的属性都可以通过这样方式,达到精准的值。

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:实现精准的流体排版原理
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址