使用 CSS Clamp 的现代流体排版

已发表: 2022-03-10
快速总结↬在本文中,我们将探讨流畅的排版原则、用例、最佳实践、使用 CSS 钳位函数的实现以及如何计算正确的参数。 我们还将学习如何解决一些可访问性问题,并注意一个我们目前无法解决但无论如何都必须注意的重要问题。

Web 开发中流体排版的概念已经存在多年,开发人员不得不依靠各种变通方法使其在浏览器中工作。 借助新的 CSS clamp功能,创建流畅的排版从未如此简单。

通常,当我们实现响应式排版时,值会在特定断点处发生变化。 它们是明确定义的。 因此设计人员经常为两个、三个甚至更多的屏幕尺寸提供排版值(字体大小、行高、字母间距等),而开发人员通常通过添加媒体查询来针对特定断点来实现这些要求。

该图显示了视口宽度和具有媒体查询断点的不同印刷值之间的依赖关系。
(大预览)

尽管排版元素可能看起来和设计上的一样好,但对于接近断点的视口宽度上的某些元素来说,情况可能并非如此。 正如我们已经知道的那样,除了设计中提到的之外,还有许多不同的设备和屏幕尺寸可供用户使用。 在中间添加更多断点和样式覆盖可能会解决问题,但我们可能会增加代码的复杂性,创建更多的边缘情况,并使代码不太清晰和可维护。

在具有不同屏幕尺寸的三种不同设备上的排版示例,即:手机、平板电脑和笔记本电脑。
尽管在较低和较高视口宽度上的排版看起来不错,但由于固定的排版值和较少的空白,断点(中心图像)附近的标题大小看起来不合适。 (大预览)

流体排版根据视口宽度在最小值和最大值之间平滑缩放。 它通常以最小值开始并保持恒定值,直到它开始增加的特定屏幕宽度点。 一旦它在另一个屏幕宽度处达到最大值,它就会从那里保持该最大值。 我们将在本文中看到,流畅的排版也可以以相反的顺序流动——从最大值开始,以最小值结束。

该图显示了视口宽度与具有最小值终点和最大值起点的不同印刷值之间的依赖性。
(大预览)

这种方法减少或消除了对特定断点和其他边缘情况的微调。 虽然它主要用于排版,但这种流体大小的方法也适用于边距、填充、间隙等。

请注意在以下示例中,标题文本如何平滑缩放,以及它在任何视口宽度上的外观如何。 另外,请注意内容如何仍然保留响应式排版,并且值仅在断点处更改。

标题随着视口宽度平滑缩放,并且我们没有像前面示例中那样在断点周围的大小不一致。
标题随着视口宽度平滑缩放,并且我们没有像前面示例中那样在断点周围出现大小不一致的情况。

尽管流体排版解决了上述问题,但它并非适用于所有场景,也不应将流体排版视为响应式排版的替代品。 每个都有自己的一组最佳实践和适当的用例,我们将在本文后面介绍这些内容。

在本文中,我们将深入探讨流体排版,并查看开发人员过去使用的各种方法。 我们还将介绍 CSS clamp函数以及它如何简化流体排版实现,我们将学习如何微调clamp函数参数以控制流体行为的起点和终点。 我们还将讨论可访问性问题,其中大部分问题今天都可以解决,以及一个我们目前还无法解决的重要可访问性问题。

跳跃后更多! 继续往下看↓

流体排版的首次尝试

作为开发人员,我们经常使用 JavaScript 来补充缺少的 CSS 功能,直到它们在主流浏览器中得到开发和支持。 在响应式网页设计的早期,像 FlowType.JS 这样的 JavaScript 库已被用于实现流畅的排版。

流体排版的第一个真正的 CSS 实现伴随着 CSS calc和 viewport 单位( vwvh )的引入。

 /* Fixed minimum value below the minimum breakpoint */ .fluid { font-size: 32px; } /* Fluid value from 568px to 768px viewport width */ @media screen and (min-width: 568px) { .fluid { font-size: calc(32px + 16 * ((100vw - 568px) / (768 - 568)); } } /* Fixed maximum value above the maximum breakpoint */ @media screen and (min-width: 768px) { .fluid { font-size: 48px; } }

这个片段看起来有点复杂,计算中涉及到很多数字。 因此,让我们将其分解为多个部分,并对正在发生的事情进行高级概述。 让我们关注选择器和媒体查询,看看它们涵盖的情况。

 .fluid { /* Min value */ } @media screen and (min-width: [breakpoint-min]) { .fluid { /* Preferred value between the minimum and maximum bound */ } @media screen and (min-width: [breakpoint-max]) { /* Max value */ }

在移动优先的方法中,第一个选择器将值固定到最小界限。 第一个媒体查询处理两个断点之间的流动行为。 最后的断点将值固定到最大界限。 现在我们知道了每个选择器和媒体查询的作用,让我们看看如何应用最小和最大界限以及如何计算流体值。

 .fluid { font-size: [value-min]; } @media (min-width: [breakpoint-min]) { .fluid { font-size: calc([value-min] + ([value-max] - [value-min]) * ((100vw - [breakpoint-min]) / ([breakpoint-max] - [breakpoint-min]))); } } @media (min-width: [breakpoint-max]) { .fluid { font-size: [value-max] } }

这是很多样板代码来实现一个非常简单的任务,即在最小和最大边界之间固定一个值,并在两个断点之间添加一个流动的行为。

尽管所需的样板数量很多,但这种方法在处理一般流体尺寸方面变得如此流行,因此很明显需要一种更简化的方法。 这就是 CSS 钳位函数的用武之地。

CSS clamp功能

CSS clamp函数采用三个值——最小界限、首选值和最大界限,并将当前值钳制在这些界限之间。 首选值用于确定界限之间的值。 首选值通常包括视口单位、百分比或其他相关单位,以实现流体效果。 这是一个强大而灵活的函数,除了固定值之外,它甚至可以接受数学函数和表达式,以及来自attr函数的值。

 clamp([value-min], [value-preferred], [value-max]);

此函数可应用于任何接受有效值类型(如长度、频率、时间、角度、百分比、数字等)的属性,因此它可以在排版和大小调整之外使用。

在撰写本文时,浏览器对clamp功能的支持率超过 90%,因此它已经得到很好的支持。 对于像 Internet Explorer 这样不受支持的桌面浏览器,提供一个备用值就足够了,因为不受支持的浏览器如果无法解析clamp函数,将忽略整个font-size表达式。

 font-size: [value-fallback]; /* Fallback value */ font-size: clamp([value-min], [value-preferred], [value-max]);

带有 CSS clamp的流体排版

让我们使用 CSS clamp函数并使用以下值填充它:

  • 最小值— 等于最小字体大小。
  • 最大值— 等于最大字体大小。
  • 首选值——决定了流畅的排版如何扩展——流畅的行为和变化速度的起点和终点。 该值将取决于视口大小,因此我们将使用视口宽度单位vw

让我们看一下以下示例,并将字体大小设置为介于32px48px之间的值。 以下font-size的设置最小值为32px ,最大值为48px 。 当前值由视口宽度单位确定,或者更准确地说,如果该值位于最小和最大边界之间,则为当前视口宽度的4%

 font-size: clamp(32px, 4vw, 48px);

让我们快速看一下将根据视口宽度应用于此示例的值,以便我们可以很好地掌握 CSS 钳制功能的工作原理。

视口宽度 (px) 首选值 (px) 应用值 (px)
500 20 32(限制在最小范围)
900 36 36(范围内的首选值)
1400 56 48(限制在最大范围内)

我们可以注意到这个钳位函数值的两个问题:

  • 无法访问 min 和 max 的像素值。
    最小和最大边界用像素值表示,因此如果用户更改其首选字体大小,它们将不会缩放。
  • 首选值的视口值不可访问。
    与上一个案例相同。 该值仅取决于视口宽度,并且不考虑用户偏好。
  • 首选值尚不清楚。
    我们使用的是4vw ,起初它可能看起来像一个神奇的数字。 我们需要知道流体行为何时开始和结束,以便我们可以同步各种流体字体大小的变化。

我们可以通过将px值除以 16(默认浏览器字体大小)将px值转换为最小和最大边界的rem值来轻松解决第一个问题。 通过这样做,最小值和最大值将适应用户浏览器的偏好。

 font-size: clamp(2rem, 4vw, 3rem);
像素值不适应浏览器字体大小偏好,但 rem 和 em 值会适应。
像素值不适应浏览器字体大小偏好,但remem值会适应。 (大预览)

我们需要对首选值采取不同的方法,因为该值需要响应视口大小。 但是,我们可以通过将相对rem值转换为数学表达式来轻松混合它。

 font-size: clamp(2rem, 4vw + 1rem, 3rem);

请注意,这不是解决所有可访问性问题的万无一失的解决方案,因此测试流畅的排版是否可以放大到足够的程度以及它是否对用户可访问性偏好做出了足够好的响应仍然很重要。 稍后我们将讨论这些问题。

但是,我们仍然不知道如何从示例中获得首选值 ( 4vw + 1rem ) 以实现所需的流体行为,所以让我们看看如何微调首选值并充分理解其背后的数学原理.

流体施胶功能

首选值会影响流体排版功能的行为方式。 更准确地说,我们可以改变最小值在哪个视口宽度点开始变化,以及在哪个视口宽度点达到最大值。

例如,我们可能希望流体行为从视口宽度的 1200 像素开始,到视口800px1200px结束。 请注意,不同的最小和最大界限需要不同的首选值(视口值和相对大小)以保持各种流体排版同步。

例如,我们通常不希望一种流体行为发生在视口宽度的1200px800px之间,而另一种流体行为发生在视口宽度的1000px750px之间。 这可能会导致大小不一致,如下例所示。

在此示例中,我们设置了四个具有不同边界和相同首选大小 4vw 的流畅排版值。尽管左侧的结果看起来一致,但流体尺寸(右侧)在某些视口宽度上不一致,因为变化发生在不同的视口宽度上。
在此示例中,我们设置了四个具有不同边界和相同首选大小的流体排版值4vw 。 尽管左侧的结果看起来一致,但流体尺寸(右侧)在某些视口宽度上不一致,因为变化发生在不同的视口宽度上。 (大预览)

为了避免这个问题,我们需要弄清楚首选值是如何计算的,并将正确的视口和相对值分配给钳制函数的首选值。

让我们找出一个用于计算它的函数。

 font-size: clamp([min]rem, [v]vw + [r]rem, [max]rem);

$$y=\frac{v}{100}*x + r$$

  • x - 当前视口宽度值 ( px )。
  • y — 当前视口宽度值x ( px ) 的结果流体字体大小。
  • v — 影响流体值变化率 ( vw ) 的视口宽度值。
  • r — 相对大小等于浏览器字体大小。 默认值为16px

使用此函数,我们可以轻松计算流体行为的起点和终点。 对于我们的示例,最小值2rem ( 32px ) 是恒定的,直到400px视口宽度。

$$32=\frac{4}{100}*x + 16$$

$$16=\frac{1}{25}*x$$

$$x=400$$

我们可以对最大值应用相同的函数,并看到它在800px视口宽度上达到最大值3rem ( 48px )。

此示例的目的只是为了演示首选值如何影响流畅的排版行为。 让我们将相同的函数用于稍微更真实的场景并解决更实际的真实示例。 我们将根据所需的字体大小和我们希望流体行为发生的特定点创建可访问的流体排版。

根据特定的起点和终点计算首选值参数

让我们看一个在现实世界场景中经常出现的实际示例。 设计师为我们提供了字体大小和断点,作为开发人员,我们需要使用以下参数来实现流畅的排版:

  • 最小字体大小为36px (y1)
  • 最大字体大小为52px (y2)
  • 最小值应以600px视口宽度 (x1) 结束
  • 最大值应从1400px视口宽度 (x2) 开始
从示例中可视化流畅的排版要求。
从示例中可视化流畅的排版要求。 (大预览)

让我们将这些值添加到我们之前讨论过的流体尺寸函数中。

$$y=\frac{v}{100} \cdot x + r$$

我们最终得到了两个方程,其中有两个参数需要计算——视口宽度值v和相对大小r

$$(1)\;\;\; y_1=\frac{v}{100} \cdot x_1 + r$$

$$(2) \;\;\; y_2 =\frac{v}{100} \cdot x_2 + r$$

我们可以取第一个等式并将其转换为我们可以使用的以下表达式。

$$(1) \;\;\; r=y_1 - \frac{v}{100} \cdot x_1$$

我们可以用这个表达式替换第二个等式中的r并得到计算v的函数。

$$v=\frac{100 \cdot (y_2-y_1)}{x_2 - x_1}$$

$$v=\frac{100 \cdot (52-36)}{1400 - 600}$$

$$v=2$$

我们得到视口宽度值2vw 。 以类似的方式,我们可以隔离r并使用可用参数计算它。

$$r=\frac{x_1y_2 - x_2y_1}{x_1 - x_2}$$

$$r=\frac{600 \cdot 52 - 1400 \cdot 36}{600 - 1400}$$

$$r=24$$

注意:这个值是以像素为单位的,相对值需要用rem表示,所以我们将像素值除以16 ,最终得到1.5rem

我们还需要将52px 36px最大边界转换为rem并将所有值添加到 CSS clamp函数中。

 font-size: clamp(2.25rem, 2vw + 1.5rem, 3.25rem);

我们可以绘制这个函数来确认计算的值是正确的。

(大预览)

总而言之,我们可以使用以下两个函数从字体大小和视口宽度点计算首选值参数v (以vw表示)和r (以rem表示)。

$$v=\frac{100 \cdot (y_2-y_1)}{x_2 - x_1}$$

$$r=\frac{x_1y_2 - x_2y_1}{x_1 - x_2}$$

现在我们完全了解了clamp函数的工作原理以及首选值是如何计算的,我们可以轻松地在我们的项目中创建一致且易于访问的流体排版,并避免上述陷阱。

使用负视口值进行流体调整

我们还可以通过对视口值使用负值来使大小随着视口大小的减小而扩大。 负视口值将反转默认的流体行为。 我们还需要通过求解前面示例中的两个上述方程来调整相对大小,以便流体行为在某些点开始和结束。

 font-size: clamp(3rem, -4vw + 6rem, 4.5rem);

我没有在我的项目中使用过这种反向配置,但是如果您在项目或设计中遇到过这种需求,您可能会发现它很有趣。

具有负值的视口大小的流体调整大小。请注意,尺寸随着视口宽度的增加而减小。
具有负值的视口大小的流体调整大小。 请注意,尺寸随着视口宽度的增加而减小。 (大预览)
流体行为从最大值开始,直到达到某个点,然后开始下降,直到达到最小值。
流体行为从最大值开始,直到达到某个点,然后开始下降,直到达到最小值。

流体排版可视化工具

当我在做一个项目时,我必须创建多种不同的流体排版配置。 我正在测试浏览器中的配置,我想创建一个工具来帮助开发人员可视化和微调流畅的排版行为。 我受到 Josh W. Comeau 的“面向 JS 开发人员的 CSS”课程中的一个演示的启发,并创建了 Modern Fluid Typography Tool。

图形视图允许开发人员对流体行为有一个总体的了解。
图形视图允许开发人员对流体行为有一个总体的了解。 跳到计算器。

开发人员可以使用此工具创建和微调流畅的排版代码片段并可视化流畅的行为以保持多个实例同步。 该工具还可以生成指向配置的链接,因此开发人员可以将链接包含在代码注释或文档中,以便其他人可以轻松检查流体调整行为。

表格视图允许开发人员在可自定义的断点列表上跟踪流体大小。
表格视图允许开发人员在可自定义的断点列表上跟踪流体大小。 (大预览)

该项目是免费和开源的,因此请随时报告任何错误并做出贡献。 我很高兴听到您的想法和功能要求!

可访问性问题

重要的是要重申,使用rem值不会自动使所有用户都可以使用流畅的排版,它只允许字体大小响应用户的字体偏好。 将 CSS clamp功能与视口单元结合使用来实现流体大小会带来另一组我们需要考虑的缺点

Adrian Roselli 在他的博客文章中对这些问题进行了广泛的测试和记录。

“当您使用vw单位或限制使用clamp()获得的文本大小时,用户可能无法将文本缩放到其原始大小的 200%。 如果发生这种情况,这是 1.4.4 Resize text (AA) 下的 WCAG 失败,因此请务必使用缩放测试结果。”

— 阿德里安·罗塞利

我想从一开始就解决这个问题,方法是使用 JavaScript 检测缩放事件何时发生,并应用一个类,该类将使用常规rem值覆盖流体大小。

 /* Apply fluid typography for default zoom level (not zoomed) */ .title { font-size: clamp(2rem, 4vw + 1rem, 3rem); } /* Revert to responsive typography if zoom is active */ body.zoom-active .title { font-size: 2rem; } @media screen and (min-width: 768px) { body.zoom-active .title { font-size: 3rem; } }

您可能会感到惊讶,因为我发现我们无法使用 JavaScript 可靠地检测缩放事件,就像我们可以检测任何其他常规视口事件(如调整大小)一样。

在撰写本文时,有 92% 的浏览器支持的 Visual Viewport API 规范,但缩放(缩放级别)值根本不起作用——无论缩放(缩放)值如何,它都会返回相同的值。 更不用说没有可用的文档、工作示例或用例。 考虑到这个 API 有如此可靠的浏览器支持,这有点奇怪。 确实存在一些解决方法,但它们也不是完全可靠的,并且无法检测页面在首次加载时是否已被放大,只有在事件发生后才能检测到。

如果 Visual Viewport API 按预期工作,我们可以轻松地在缩放事件上切换 CSS 类。

 /* This code won't work because visualViewport.scale is buggy * and always returns the same value. This might be fixed in the future. */ function checkZoomLevel() { if (window.visualViewport.scale === 1) { document.body.classList.remove("zoom-active"); } else { document.body.classList.add("zoom-active"); } } window.addEventListener("resize", checkZoomLevel);

不幸的是,通过应用可变大小,我们冒着使某些在浏览时使用缩放功能的用户无法访问内容的风险。 在我们可以为流畅排版创建可靠且更易于访问的备用方案之前,请确保谨慎使用流畅大小并测试缩放级别是否符合 Web 内容可访问性指南 (WCAG)。

推荐用例

流畅的排版最适合大型和突出的文本元素,最小和最大尺寸之间的差异较大。 如果没有相应地缩放,大标题在较小的视口上会显得更加刺耳和格格不入。

流体施胶也推荐用于我们需要保持一致施胶的情况。

在较小的桌面和较大的平板电脑视口上,标题大小和容器网格间隙未正确缩放到卡片尺寸。我们可以通过使用流体尺寸来解决这个问题。
在较小的桌面和较大的平板电脑视口上,标题大小和容器网格间隙未正确缩放到卡片尺寸。 我们可以通过使用流体尺寸来解决这个问题。 (大预览)
流体大小可用于保持排版缩放和一致的网格间隙
流体大小可用于保持排版缩放和一致的网格间隙。

Elise Hein 在她关于流畅排版最佳实践的文章中得出了类似的结论。

“我尝试并未能找到许多特定区域,其中相对于视口的排版在可读性方面确实优于基于断点的大小。 这里有两个:设置显示文本保持一致的度量。”

— 伊莉丝·海因

如果最小值和最大值之间的差异只有几个像素,那么流畅的排版就不会像正文中的常见情况那样有效或有用。 最小和最大字体大小之间的差异很小的正文文本在任何视口宽度上都不会显得格格不入,因为较大的字体大小就是这种情况。 对于这些情况,建议使用带有断点的常规响应式排版。

结论

流体排版不应该作为响应式排版的替代品,而是作为特定用例的增强。 我们应该使用流畅的排版来平滑缩放最小和最大尺寸之间差异较大的文本,并保持一致的尺寸。

当使用具有 CSS clamp功能的多个流体排版元素时,我们必须确保流体缩放是同步的。 我们可以通过计算视口宽度和相对值并将它们用作 CSS clamp函数中的首选值来做到这一点。 我们还必须记住使用相对单位,如 rem 单位,以便流畅的排版适应用户的字体大小偏好。

我们还看到了流畅的排版如何限制用户缩放功能,从而导致可访问性问题。 如果测试显示内容不够可缩放,则使用缩放测试流畅的排版并将其恢复为常规响应式排版非常重要。

当缩放动作发生时,我们应该能够通过覆盖流体排版值来解决这个问题。 但是,目前无法这样做,因为 Visual Viewport API 无法正常工作并且不响应用户缩放事件。

参考

  • clamp() ,MDN
  • “无论如何,为什么类型应该是流畅的?” Elise Hein
  • “简化的流体排版,” Chris Coyier
  • “响应式类型和缩放,”阿德里安·罗塞利
  • “使用vhvw单位的响应式和流畅的排版,”Michael Riethmuller