使用 CSS Poly Fluid Sizing 的流体响应式排版

已发表: 2022-03-10
快速总结↬流体布局多年来一直是前端开发的正常部分。 然而,流体排版的想法相对较新,尚未得到充分探索。 到目前为止,大多数开发人员对流体排版的想法只是简单地使用视口单元,可能具有一些最小和最大尺寸。 在本文中,我们将把它提升到另一个层次。 我们将研究如何使用支持良好的浏览器功能和一些基本代数,跨多个断点和预定义的字体大小创建可扩展、流畅的排版。 最好的部分是您可以使用 Sass 将这一切自动化。

在本文中,我们将把它提升到另一个层次。 我们将研究如何使用支持良好的浏览器功能和一些基本代数,跨多个断点和预定义的字体大小创建可扩展、流畅的排版。 最好的部分是您可以使用 Sass 将这一切自动化。

关于 SmashingMag 的进一步阅读

  • 具有 vh 和 vw 单位的真正流畅的排版
  • HTML 电子邮件通讯设计中的排版模式
  • Web 排版的好、坏和伟大的例子
  • 用于更有意义的 Web 排版的工具和资源

在与创意设计师合作进行网页设计时,收到多个 Sketch 或 Photoshop 画板/布局是很常见的,每个断点一个。 在该设计中,元素(如h1标题)通常在每个断点处具有不同的大小。 例如:

跳跃后更多! 继续往下看↓
  1. 小布局的h1可以是22px
  2. 中等布局的h1可以是24px
  3. 大布局的h1可能是34px

最低限度的 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; } }

这是一个很好的第一步,但是您将font-size限制为仅由设计人员在提供的断点处指定的大小。 如果你问,“在 850 像素宽的视口中, font-size应该是多少?”设计师会怎么说? 大多数情况下的答案是它在 24px 和 34px 之间。 但是现在,根据您的 CSS,它只有 24px,这可能不是设计师所设想的。

此时您的选择是计算该大小应该是多少并添加另一个断点。 这很容易。 但是所有其他决议呢? 800px 宽的font-size应该是多少? 900像素呢? 935像素呢? 显然,设计师不会为每个可能的分辨率提供完整的布局。 即使他们这样做了,您是否应该为设计师所需的所有不同font-sizes添加数十个(或数百个)断点? 当然不是。

您的布局已经随着视口的宽度流畅地缩放。 如果您的排版可预测地与您的流体布局一起缩放,那不是很好吗? 我们还能做些什么来改进这一点?

视口单位来救援?

视口单位是朝着正确方向迈出的又一步。 它们允许您的文本随着您的布局流畅地调整大小。 这些天浏览器支持很棒。

我可以使用视口吗?
(查看大图)

但是视口单元的可行性非常依赖于网页的原始创意设计。 使用vw设置font-size并完成:

 h1 { font-size: 2vw; } 

但这只有在您的创意画板考虑到这一点时才有效。 设计师是否选择了正好是他每个画板宽度的 2% 的文本大小? 当然不是。 让我们计算一下每个断点需要的vw值:

22px尺寸 @ 576px宽 = 22576 *100 = 3.82vw 24px尺寸 @ 768px宽 = 24768 *100 = 3.13vw 34px尺寸 @ 992px宽 = 34992 *100 = 3.43vw

它们很接近,但并不完全相同。 因此,您仍然需要使用媒体查询在文本大小之间进行转换,并且仍然会有跳转。 并考虑这个奇怪的副作用:

@ 767px,视口宽度的 3.82% 为 29px。 如果视口宽 1 像素,则font-size会突然下降到 24px 。 调整视口大小的动画演示了这种不良副作用:

字体大小的这种戏剧性变化几乎绝对不是设计师所设想的。 那么我们如何解决这个问题呢?

统计线性回归?

等待。 什么? 是的,这是一篇关于 CSS 的文章,但是一些基本的数学知识可以大大有助于我们的问题的优雅解决方案。

首先,让我们在图表上绘制我们的分辨率和相应的文本大小:

字体大小和相应视口宽度的散点图
font-size和相应视口宽度的散点图(Google 电子表格)(查看大图)

在这里,您可以看到设计人员在定义的视口宽度下指定的文本大小的散点图。 x 轴是视口宽度,y 轴是font-size 。 看到那条线了吗? 这称为趋势线。 这是一种根据提供的数据为任何视口宽度查找内插font-size值的方法。

趋势线是这一切的关键

如果您可以根据此趋势线设置font-size ,您将拥有一个在所有分辨率上平滑缩放的 h1,这将接近于与设计师的意图相匹配。 首先,让我们看一下数学。 直线由以下等式定义:

线性方程定义
线性方程定义
  • m = 斜率
  • b = y 截距
  • x = 当前视口宽度
  • y = 结果font-size

有几种确定斜率和 y 截距的方法。 当涉及多个值时,常用的方法是最小二乘拟合:

最小二乘

一旦你运行了这些计算,你就有了趋势线方程。

我如何在 CSS 中使用它?

好的,这在数学上变得相当沉重。 我们如何在前端 Web 开发中实际使用这些东西? 答案是 CSS calc() ! 再一次,一个相当新的 CSS 技术得到了很好的支持。

我可以使用计算器吗?
(查看大图)

您可以像这样使用趋势线方程:

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

找到斜率和 y 截距后,只需将它们插入即可!

注意:您必须将斜率乘以100 ,因为您将其用作视口宽度的 1/100 的vw单位。

这可以自动化吗?

我将最小二乘拟合方法移植到一个易于使用的 Sass 函数中:

 /// least-squares-fit /// 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: least-squares-fit((576px: 24px, 768px: 24px, 992px: 34px)); /// @author Jake Wilson <[email protected]> @function least-squares-fit($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}); }

这真的有效吗? 打开这个 CodePen 并调整浏览器窗口的大小。 有用! 字体大小与原始设计的要求相当接近,并且可以随着您的布局平滑缩放。

最小二乘拟合 SCSS 测试 user="jakobud"]请参阅 Jake Wilson (@jakobud) 在 CodePen 上的笔最小二乘拟合 SCSS 测试。

最小二乘拟合 SCSS 测试 user=“jakobud”]查看 Jake Wilson (@jakobud) 在 CodePen 上的笔最小二乘拟合 SCSS 测试。

现在,诚然,它并不完美。 这些值接近原始设计,但并不完全匹配。 这是因为线性趋势线是特定视口宽度下特定字体大小的近似值。 这是线性回归的继承。 你的结果总是有一些错误。 这是简单性与准确性的权衡。 另外,请记住,您的文本大小越多,趋势线中的错误就越多。

我们能做得比这更好吗?

多项式最小二乘拟合

为了获得更准确的趋势线,您需要查看更高级的主题,例如可能看起来像这样的多项式回归趋势线:

多项式回归趋势线
多项式回归趋势线(Google 电子表格)(查看大图)

现在更像了! 比我们的直线要准确得多。 一个基本的多项式回归方程如下所示:

3 次多项式方程
3 次多项式方程

你想要的曲线越准确,方程就越复杂。 不幸的是,你不能在 CSS 中做到这一点calc()根本不能做这种类型的高级数学。 具体来说,您无法计算指数:

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

因此,在calc()支持这种类型的非线性数学之前,我们只能使用线性方程。 我们还能做些什么来改进这一点吗?

断点和多重线性方程

如果我们只计算每对断点之间的直线会怎样? 像这样的东西:

多对值之间的线性回归趋势线
多对值之间的线性回归趋势线(Google 电子表格)(查看大图)

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

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

我们可以对这些calc()值使用最小二乘拟合方法,但由于它只是两点之间的直线,因此可以大大简化数学运算。 还记得直线方程吗?

线性方程定义
线性方程定义

由于我们现在只讨论 2 个点,因此找到斜率 (m) 和 y 截距 (b) 很简单:

求线性方程的斜率和 y 截距
求线性方程的斜率和 y 截距

这是一个 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 <[email protected]> @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}); }

现在,只需在 Sass 中的多个断点上使用线性插值函数。 另外,让我们输入一些最小和最大font-sizes

 // SCSS h1 { // Minimum font-size font-size: 22px; // Font-size between 576 - 768 @media (min-width:576px) { $map: (576px: 22px, 768px: 24px); font-size: linear-interpolation($map); } // Font-size between 768 - 992 @media (min-width:768px) { $map: (768px: 24px, 992px: 34px); font-size: linear-interpolation($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; } } 

CSS 大小调整的圣杯?

让我们用一个漂亮的 Sass mixin 把这一切包装起来(为了懒惰和高效!)。 我正在创造这种方法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 <[email protected]> @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)) { $value1: map-get($map, nth($keys,$i)); $value2: map-get($map, nth($keys,($i + 1))); // If values are not equal, perform linear interpolation @if ($value1 != $value2) { #{$property}: linear-interpolation((nth($keys,$i): $value1, nth($keys,($i+1)): $value2)); } @else { #{$property}: $value1; } } } // Maxmimum size @media (min-width:nth($keys,$length)) { #{$property}: map-get($map, nth($keys,$length)); } }

这个 Sass mixin 需要以下 Github gists 中的一些 Sass 函数:

  • 线性插值
  • 地图排序
  • 列表排序
  • 列表删除

poly-fluid-sizing() mixin 将对每对视口宽度执行线性插值并设置最小和最大尺寸。 您可以将其导入任何 Sass 项目并轻松使用它,而无需了解其背后的任何数学知识。 这是使用此方法的最终 CodePen。

Poly Fluid Sizing using linear equations, viewport units and calc() user="jakobud"]参见 Pen Poly Fluid Sizing using linear equations, viewport units and calc()"] Poly Fluid Sizing using linear equations, viewport units and calc()作者:Jake Wilson (@jakobud) 在 CodePen 上。

Poly Fluid Sizing using linear equations, viewport units and calc() user=“jakobud”]参见 Pen Poly Fluid Sizing using linear equations, viewport units and calc()“] Poly Fluid Sizing using linear equations, viewport units and calc()作者:Jake Wilson (@jakobud) 在 CodePen 上。

几点注意事项

  • 显然,这种方法不仅适用于font-size ,还适用于任何单位/长度属性( marginpadding等)。 您将所需的属性名称作为字符串传递给 mixin。
  • 视口宽度 + 大小值对的 Sass 映射可以按任何顺序传递到poly-fluid-sizing()混合中。 它将根据视口宽度从最低到最高自动对地图进行排序。 所以你可以传入这样的地图,它会很好地工作:
 $map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px); @include poly-fluid-sizing('font-size', $map);
  • 此方法的一个限制是您不能将混合单位传递给 mixin。 例如, 3em @ 576px宽度。 Sass 只是真的不知道在数学上该做什么。

结论

这是我们能做的最好的吗? Poly Fluid Sizing 是 CSS 中流体单元大小调整的圣杯吗? 可能是。 CSS 目前支持非线性动画和过渡计时功能,所以也许calc()有一天也会支持它。 如果发生这种情况,非线性多项式回归可能值得再次研究。 但也许不是……无论如何,线性缩放可能更胜一筹。

我在 2017 年初开始探索这个想法,并最终开发了上述解决方案。 从那以后,我看到一些开发人员提出了类似的想法和这个难题的不同部分。 我认为是时候分享我的方法以及我是如何到达那里的了。 视口单位。 计算()。 萨斯。 断点。 这些东西都不是新的。 它们都是存在多年的浏览器功能(具有不同程度的支持)。 我只是以尚未完全探索的方式将它们一起使用。 不要害怕查看您每天使用的工具,并开箱即用地思考如何更好地利用它们并提高您的技能。