使用 CSS Poly Fluid Sizing 的流體響應式排版
已發表: 2022-03-10在本文中,我們將把它提升到另一個層次。 我們將研究如何使用支持良好的瀏覽器功能和一些基本代數,跨多個斷點和預定義字體大小創建可擴展、流暢的排版。 最好的部分是您可以使用 Sass 將這一切自動化。
關於 SmashingMag 的進一步閱讀:
- 具有 vh 和 vw 單位的真正流暢的排版
- HTML 電子郵件通訊設計中的排版模式
- Web 排版的好、壞和偉大的例子
- 用於更有意義的 Web 排版的工具和資源
在與創意設計師合作進行網頁設計時,收到多個 Sketch 或 Photoshop 畫板/佈局是很常見的,每個斷點一個。 在該設計中,元素(如h1
標題)通常在每個斷點處具有不同的大小。 例如:
- 小佈局的
h1
可以是22px
- 中等佈局的
h1
可以是24px
- 大佈局的
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
寬 = 22 ⁄ 576 *100 = 3.82vw 24px
尺寸 @ 768px
寬 = 24 ⁄ 768 *100 = 3.13vw 34px
尺寸 @ 992px
寬 = 34 ⁄ 992 *100 = 3.43vw
它們很接近,但並不完全相同。 因此,您仍然需要使用媒體查詢在文本大小之間進行轉換,並且仍然會有跳轉。 並考慮這個奇怪的副作用:
@ 767px,視口寬度的 3.82% 為 29px。 如果視口寬 1 像素,則font-size
會突然下降到 24px 。 調整視口大小的動畫演示了這種不良副作用:
字體大小的這種戲劇性變化幾乎絕對不是設計師所設想的。 那麼我們如何解決這個問題呢?
統計線性回歸?
等待。 什麼? 是的,這是一篇關於 CSS 的文章,但是一些基本的數學知識可以大大有助於我們的問題的優雅解決方案。
首先,讓我們在圖表上繪製我們的分辨率和相應的文本大小:
在這裡,您可以看到設計人員在定義的視口寬度下指定的文本大小的散點圖。 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 並調整瀏覽器窗口的大小。 有用! 字體大小與原始設計的要求相當接近,並且可以隨著您的佈局平滑縮放。
現在,誠然,它並不完美。 這些值接近原始設計,但並不完全匹配。 這是因為線性趨勢線是特定視口寬度下特定字體大小的近似值。 這是線性回歸的繼承。 你的結果總是有一些錯誤。 這是簡單性與準確性的權衡。 另外,請記住,您的文本大小越多,趨勢線中的錯誤就越多。
我們能做得比這更好嗎?
多項式最小二乘擬合
為了獲得更準確的趨勢線,您需要查看更高級的主題,例如可能看起來像這樣的多項式回歸趨勢線:
現在更像了! 比我們的直線要準確得多。 一個基本的多項式回歸方程如下所示:
你想要的曲線越準確,方程就越複雜。 不幸的是,你不能在 CSS 中做到這一點。 calc()
根本不能做這種類型的高級數學。 具體來說,您無法計算指數:
font-size: calc(3vw * 3vw); /* This doesn't work in CSS */
因此,在calc()
支持這種類型的非線性數學之前,我們只能使用線性方程。 我們還能做些什麼來改進這一點嗎?
斷點和多重線性方程
如果我們只計算每對斷點之間的直線會怎樣? 像這樣的東西:
所以在這個例子中,我們將計算22px
和24px
之間的直線,然後計算24px
和34px
之間的另一條直線。 Sass 看起來像這樣:
// SCSS h1 { @media (min-width:576px) { font-size: calc(???); } @media (min-width:768px) { font-size: calc(???); } }
我們可以對這些calc()
值使用最小二乘擬合方法,但由於它只是兩點之間的直線,因此可以大大簡化數學運算。 還記得直線方程嗎?
由於我們現在只討論 2 個點,因此找到斜率 (m) 和 y 截距 (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 <[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。
幾點注意事項
- 顯然,這種方法不僅適用於
font-size
,還適用於任何單位/長度屬性(margin
、padding
等)。 您將所需的屬性名稱作為字符串傳遞給 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 年初開始探索這個想法,並最終開發了上述解決方案。 從那以後,我看到一些開發人員提出了類似的想法和這個難題的不同部分。 我認為是時候分享我的方法以及我是如何到達那裡的了。 視口單位。 計算()。 薩斯。 斷點。 這些東西都不是新的。 它們都是存在多年的瀏覽器功能(具有不同程度的支持)。 我只是以尚未完全探索的方式將它們一起使用。 不要害怕查看您每天使用的工具,並開箱即用地思考如何更好地利用它們並提高您的技能。