Typografia reagująca na płyny z wymiarowaniem polipłynowym CSS
Opublikowany: 2022-03-10W tym artykule zamierzamy przenieść to na inny poziom. Zamierzamy zbadać , jak stworzyć skalowalną, płynną typografię w wielu punktach przerwania i predefiniowanych rozmiarach czcionek przy użyciu dobrze obsługiwanych funkcji przeglądarki i podstawowej algebry. Najlepsze jest to, że możesz to wszystko zautomatyzować za pomocą Sassa.
Dalsze czytanie na SmashingMag:
- Prawdziwie płynna typografia z jednostkami vh i vw
- Wzory typograficzne w projekcie biuletynu e-mail w formacie HTML
- Dobre, złe i świetne przykłady typografii internetowej
- Narzędzia i zasoby dla bardziej znaczącej typografii internetowej
Podczas pracy z kreatywnymi projektantami nad projektami stron internetowych dość często otrzymuje się wiele obszarów roboczych/układów programu Sketch lub Photoshop, po jednym dla każdego punktu przerwania. W tym projekcie elementy (takie jak nagłówek h1
) będą zwykle miały różne rozmiary w każdym punkcie przerwania. Na przykład:
-
22px
na małym układzie może miećh1
-
h1
w średnim układzie może mieć24px
-
h1
na dużym układzie może mieć34px
Minimalny CSS do tego używa zapytań o media:
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; } }

To dobry pierwszy krok, ale ograniczasz font-size
tylko do tego, co zostało określone przez projektanta w podanych punktach przerwania. Co powiedziałby projektant, gdybyś zapytał: „Jaki powinien być font-size
w widoku o szerokości 850 pikseli?” Odpowiedź w większości przypadków jest taka, że będzie to gdzieś pomiędzy 24 a 34 piksele. Ale teraz to tylko 24px według twojego CSS, co prawdopodobnie nie jest tym, co wyobrażał sobie projektant.
W tym momencie Twoją opcją jest obliczenie, jaki powinien być ten rozmiar i dodanie kolejnego punktu przerwania. To dość łatwe. Ale co z wszystkimi innymi rezolucjami? Jaki powinien być font-size
przy szerokości 800px? A co z 900px? A co z 935px? Oczywiście projektant nie zapewni pełnego układu dla każdej możliwej rozdzielczości. Nawet gdyby tak było, czy powinieneś dodać dziesiątki (lub setki) punktów przerwania dla wszystkich różnych font-sizes
które są pożądane przez projektanta? Oczywiście nie.
Twój układ jest już płynnie skalowany wraz z szerokością widocznego obszaru. Czy nie byłoby miło, gdyby twoja typografia była przewidywalnie skalowana z twoim płynnym układem? Co jeszcze możemy zrobić, aby to poprawić?
Jednostki widokowe na ratunek?
Jednostki rzutni to kolejny krok we właściwym kierunku. Pozwalają na płynną zmianę rozmiaru tekstu za pomocą układów. W dzisiejszych czasach obsługa przeglądarek jest świetna.

Jednak żywotność jednostek Viewport jest bardzo zależna od oryginalnych projektów kreatywnych strony internetowej. Byłoby wspaniale ustawić font-size
za pomocą vw
i gotowe:
h1 { font-size: 2vw; }

Ale to działa tylko wtedy, gdy twoje kreatywne tablice graficzne biorą to pod uwagę. Czy projektant wybrał rozmiar tekstu, który stanowi dokładnie 2% szerokości każdej z jego artboardów? Oczywiście nie. Obliczmy, jaka powinna być wartość vw
dla każdego z naszych punktów przerwania:
22px
@ 576px
= 22 ⁄ 576 *100 = 3.82vw 24px
@ 768px
= 24 ⁄ 768 *100 = 3.13vw 34px
@ 992px
= 34 ⁄ 992 *100 = 3.43vw
Są blisko, ale nie wszystkie są takie same. Tak więc nadal musiałbyś używać zapytań o media, aby przechodzić między rozmiarami tekstu, i nadal występowałyby przeskoki. I rozważ ten dziwny efekt uboczny:
@ 767px, 3,82% szerokości widocznego obszaru to 29px. Jeśli widoczny obszar jest szerszy o 1 piksel font-size
nagle spada do 24 pikseli . Ta animacja zmiany rozmiaru rzutni pokazuje ten niepożądany efekt uboczny:

Ta dramatyczna zmiana rozmiaru czcionki prawie na pewno nie jest tym, co wyobrażał sobie projektant. Jak więc rozwiązać ten problem?
Statystyczna regresja liniowa?
Czekać. Co? Tak, to jest artykuł o CSS, ale podstawowa matematyka może pomóc w eleganckim rozwiązaniu naszego problemu.
Najpierw wykreślmy nasze rozdzielczości i odpowiadające im rozmiary tekstu na wykresie:

font-size
i odpowiadającej mu szerokości rzutni (arkusze kalkulacyjne Google) (wyświetl dużą wersję) Tutaj możesz zobaczyć wykres punktowy rozmiarów tekstu określonych przez projektanta przy zdefiniowanych szerokościach widocznego obszaru. Oś x to szerokość widocznego obszaru, a oś y to font-size
. Widzisz tę linię? To się nazywa linia trendu . Jest to sposób na znalezienie interpolowanej wartości font-size
dla dowolnej szerokości widocznego obszaru na podstawie dostarczonych danych.
Linia trendu jest kluczem do tego wszystkiego
Gdybyś mógł ustawić font-size
zgodnie z tą linią trendu, miałbyś h1, który płynnie skaluje się we wszystkich rozdzielczościach zbliżonych do zamierzeń projektanta. Najpierw spójrzmy na matematykę. Linia prosta jest zdefiniowana przez to równanie:

- m = nachylenie
- b = punkt przecięcia z osią Y
- x = bieżąca szerokość rzutni
- y = wynikowy
font-size
Istnieje kilka metod określania nachylenia i przecięcia z osią Y. Gdy w grę wchodzi wiele wartości, powszechną metodą jest dopasowanie metodą najmniejszych kwadratów:

Po wykonaniu tych obliczeń otrzymujesz równanie linii trendu.
Jak tego używać w CSS?
Dobra, matematyka zaczyna być dość ciężka. Jak właściwie używamy tych rzeczy w tworzeniu front-endowych stron internetowych? Odpowiedzią jest CSS calc()
! Po raz kolejny dość nowa technologia CSS, która jest bardzo dobrze obsługiwana.

Możesz użyć równania linii trendu w ten sposób:
h1 { font-size: calc({slope}*100vw + {y-intercept}px); }
Gdy znajdziesz swoje nachylenie i przecięcie Y, po prostu je podłącz!
Uwaga: Musisz pomnożyć nachylenie przez 100
, ponieważ używasz go jako jednostki vw
, która stanowi 1/100 szerokości rzutni.
Czy można to zautomatyzować?
Przeniosłem metodę najmniejszych kwadratów do łatwej w użyciu funkcji Sassa:
/// 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}); }
Czy to naprawdę działa? Otwórz ten CodePen i zmień rozmiar okna przeglądarki. To działa! Rozmiary czcionek są dość zbliżone do tego, o co prosił oryginalny projekt i płynnie skalują się z układem.

Test najmniejszych kwadratów pasuje do SCSS user="jakobud"]Zobacz test Pen najmniejszych kwadratów pasuje do SCSS autorstwa Jake'a Wilsona (@jakobud) na CodePen.
Teraz trzeba przyznać, że nie jest idealny. Wartości są zbliżone do oryginalnego projektu, ale nie do końca do siebie pasują. Dzieje się tak, ponieważ liniowa linia trendu jest przybliżeniem określonych rozmiarów czcionek przy określonych szerokościach widocznego obszaru. To jest dziedzictwo regresji liniowej. W wynikach zawsze jest jakiś błąd. To kompromis między prostotą a dokładnością. Pamiętaj też, że im bardziej zróżnicowane są rozmiary tekstu, tym więcej błędów pojawi się na linii trendu.
Czy możemy zrobić coś lepszego?
Dopasowanie wielomianu metodą najmniejszych kwadratów
Aby uzyskać dokładniejszą linię trendu, musisz przyjrzeć się bardziej zaawansowanym tematom, takim jak linia trendu regresji wielomianowej, która może wyglądać mniej więcej tak:

Teraz to jest bardziej podobne! Dużo dokładniejsze niż nasza linia prosta. Podstawowe równanie regresji wielomianowej wygląda tak:

Im dokładniejsza jest twoja krzywa, tym bardziej skomplikowane staje się równanie. Niestety nie możesz tego zrobić w CSS . calc()
po prostu nie może wykonać tego typu zaawansowanej matematyki. W szczególności nie możesz obliczyć wykładników:
font-size: calc(3vw * 3vw); /* This doesn't work in CSS */
Tak więc dopóki calc()
nie obsługuje tego typu matematyki nieliniowej, utknęliśmy tylko przy równaniach liniowych . Czy jest coś jeszcze, co możemy zrobić, aby to poprawić?
Punkty przerwania i wiele równań liniowych
Co by było, gdybyśmy obliczyli tylko linię prostą między każdą parą punktów przerwania? Coś takiego:

W tym przykładzie obliczylibyśmy linię prostą między 22px
a 24px
, a następnie kolejną między 24px
a 34px
. Sass wyglądałby tak:
// SCSS h1 { @media (min-width:576px) { font-size: calc(???); } @media (min-width:768px) { font-size: calc(???); } }
Moglibyśmy użyć metody najmniejszych kwadratów dla tych wartości calc()
, ale ponieważ jest to tylko linia prosta między 2 punktami, matematykę można znacznie uprościć. Pamiętasz równanie linii prostej?

Ponieważ teraz mówimy tylko o 2 punktach, znalezienie nachylenia (m) i przecięcia z osią y (b) jest trywialne:

Oto funkcja Sassa do tego:
/// 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}); }
Teraz po prostu użyj funkcji interpolacji liniowej na wielu punktach przerwania w swoim Sassie. Wrzućmy również kilka minimalnych i maksymalnych 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; } }
I generuje ten 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; } }

Święty Graal wielkości CSS?
Zawińmy to wszystko w ładną mieszankę Sass (dla leniwych i wydajnych!). Wymyślam tę metodę 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)); } }
Ten mixin Sass wymaga kilku funkcji Sass w następujących środowiskach Github:
- interpolacja liniowa
- sortowanie map
- sortowanie listy
- usuń listę
Dodatek poly-fluid-sizing()
wykona liniową interpolację na każdej parze szerokości okienka ekranu i ustawi minimalny i maksymalny rozmiar. Możesz zaimportować to do dowolnego projektu Sass i łatwo z niego korzystać bez konieczności znajomości matematyki. Oto ostatni CodePen, który używa tej metody.
Rozmiary Poly Fluid przy użyciu równań liniowych, jednostek rzutni i calc() user="jakobud"]Zobacz Pen Rozmiary Poly Fluid przy użyciu równań liniowych, jednostek rzutni i calc()"] Rozmiary Poly Fluid za pomocą równań liniowych, jednostek rzutni i calc() autorstwa Jake'a Wilsona (@jakobud) na CodePen.
Kilka uwag
- Oczywiście ta metoda ma zastosowanie nie tylko do
font-size
ale do dowolnej właściwości jednostki/długości (margin
,padding
, itp.). Przekazujesz żądaną nazwę właściwości do mixina jako ciąg. - Mapa Sass par szerokości i rozmiaru okienka ekranu może być przekazywana w dowolnej kolejności do
poly-fluid-sizing()
. Automatycznie posortuje mapę według szerokości rzutni od najniższej do najwyższej . Możesz więc przekazać mapę taką jak ta i będzie dobrze:
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px); @include poly-fluid-sizing('font-size', $map);
- Ograniczeniem tej metody jest to, że nie można wprowadzić mieszanych jednostek do mixina. Na przykład
3em
@ szerokość576px
. Sass po prostu nie będzie wiedział, co robić matematycznie.
Wniosek
Czy to najlepsze, co możemy zrobić? Czy Poly Fluid Sizing jest Świętym Graalem w zakresie doboru jednostek płynów w CSS? Być może. CSS obecnie obsługuje nieliniową animację i funkcje czasu przejścia, więc może jest szansa, że calc()
też kiedyś to wesprze. Jeśli tak się stanie, nieliniowa regresja wielomianowa może być ponownie warta przyjrzenia się. Ale może nie… Skalowanie liniowe i tak może być lepsze.
Zacząłem zgłębiać ten pomysł na początku 2017 roku i ostatecznie opracowałem powyższe rozwiązanie. Od tego czasu widziałem kilku deweloperów, którzy wymyślili podobne pomysły i różne elementy tej układanki. Pomyślałem, że nadszedł czas, abym podzielił się moją metodą i sposobem, w jaki się tam dostałem. Jednostki rzutni. Oblicz(). Sass. Punkty przerwania. Żadna z tych rzeczy nie jest nowa. Są to wszystkie funkcje przeglądarki, które istnieją od lat (o różnym stopniu wsparcia). Używałem ich tylko razem w sposób, który nie został jeszcze w pełni zbadany. Nigdy nie bój się patrzeć na narzędzia, których używasz na co dzień, i wymyśl nieszablonowe sposoby ich lepszego wykorzystania i rozwijania swoich umiejętności.