CSS Houdini - przyszłość CSSa czy niepotrzebna nowość?
- Czas potrzebny na przeczytanie:7 minut
- Opublikowane:
CSS Houdini to zbiór API, udostępnionych przez przeglądarkę, dzięki którym, mamy bezpośredni dostęp do drzewka CSSOM(CSS Object Model). Pozwala nam to rozszerzać CSSa o nowe funkcjonalności, wpinać się do silnika renderującego i mówić przeglądarce, w jaki sposób ma wykorzystać CSSa podczas renderowania. A to wszystko z pomocą JavaScriptu!
Wsparcie przeglądarek dla Houdini różni się dla każdego API, jeśli chcesz zobaczyć pełen wykres wsparcia, zachęcam Cię do odwiedzenia https://ishoudinireadyyet.com/.
Dlaczego warto?
Po pierwsze, nowości wprowadzane do CSSa często potrzebują pollyfilli, mogą być one na bazie JavaScriptu, jednak te niosą ze sobą problemy z wydajnością. Dzieje się tak dlatego, że muszą one poczekać aż DOM i CSSOM się utworzą. Gdy drzewa się stworzą i nasz dokument zostanie załadowany, kończy się pierwszy cykl renderowania, dopiero po nim nasz pollyfill może zadziałać. W CSS Houdini nie czekamy na ten pierwszy cykl renderowania.
Następną zaletą jest, powiązaną już z poprzednią, jest fakt, iż nie musimy czekać na nowości, aż zostaną wprowadzone do przeglądarek.
Spis treści
- TypedOM API
- Custom Properties And Values API
- Paint API
- Animation API
- Layout API
- Wsparcie przeglądarek
- Przyszłość CSS czy niepotrzebna nowość?
- Podsumowanie
TypedOM API
Jak możemy zmieniać CSSowe wartości w JavaScripcie?
Nic prostszego, pobieramy element, piszemy .style
i wybraną wartość:
button.style.fontSize = 32 + 'px';
Nie jest to przyjemny sposób, może rodzić wiele problemów i bugów.
computedStyleMap
W TypedOM manipulacja CSSem, jest bardziej logiczna, prostsza i szybsza. Zamiast stringów dostajemy obiekt CSSStyleValue
z kluczami i wartościami:
{
value: 20,
unit: "px"
}
const button = document.querySelector('.button');
button.computedStyleMap().get('font-size');
Wykorzystujemy tutaj metodę computedStyleMap()
, zwraca nam ona wszystkie style danego elementu z stylesheeta (computed styles). Dzięki niej mamy dostęp do metody get()
, która zwraca daną własność. Oprócz niej mamy dostęp również do metod: set()
,delete()
,has()
i append()
.
attributeStyleMap
Oprócz computedStyleMap()
mamy również dostęp do wartości attributeStyleMap
. Możemy, dzięki niej, pobierać, zamiast computed styles, wartości inlinowe.
let heightValue = element.attributeStyleMap.get('height');
heightValue.value++;
target.attributeStyleMap.set('height', heightValue);
CSSStyleValue
TypedOM udostępnia nam również klasę w której wszystkie CSSowe wartości są opisane. Dzięki temu mamy dostęp do jej subklas: CSSKeywordValue
, CSSNumericValue
,CSSTransformValue
, CSSResourceValue
.
Dzięki CSSKeywordValue
do slów kluczowych, np. none
:
element.attributeStyleMap.set("display", new CSSKeywordValue("none")));
Obiekty CSSNumericValue
możemy podzielić na CSSUnitValues
i CSSMathValues
. Ta pierwsza reprezentuje numeryczne wartości wraz z jednostkami, np. CSSUnitValue(12, 'px')
, ta druga zaś bardziej zaawansowane operacje, np. CSSMathSum(CSS.em(5), CSS.px(5))
, odpowiada to znanemu już ze zwykłego CSSa calc(5em + 5px)
.
Dzięki CSSTransformValue
możemy wpływać na wartości transform
, a CSSResourceValue
na np. background-image
(za pomocą CSSImageValues
).
Custom Properties And Values API
Custom Properties And Values pozwala nam rozszerzać zmienne CSSowe dodając do nich pewnie ciekawe ficzery.
Żeby je stworzyć używamy specjalnej metody registerProperty()
, metoda ta przyjmuje pewne argumenty:
name
- nazwasyntax
- mówi przeglądarce jak ją parsować, mamy do dyspozycji np.<color>
,<integer>
,<number>
,<percentage>
inherits
- informacja o dziedziczeniu przez rodzica, możliwe opcje:true
lubfalse
initialValue
- początkowa wartość
// JS
CSS.registerProperty({
name: '--box__gradient--position',
syntax: '<percentage>',
initialValue: '60%',
inherits: false,
});
// CSS
.box {
width: 20rem;
height: 20rem;
background: linear-gradient(
45deg,
rgba(255, 255, 255, 1) 0%,
var(--box__color) var(--box__gradient--position)
);
transition: --box__color 0.5s ease, --box__gradient--position 1s 0.5s ease;
}
.box:hover {
--box__color: #baebae;
--box__gradient--position: 0%;
}
Tym sposobem osiągnęliśmy nieosiągalne w CSS - zanimowaliśmy gradient.
Paint API
Zanim zaczniemy, Paint API jest Workletem. A co to ten Worklet? Worklety to moduły czy też skrypty działające w osobnym wątku JavaScriptu. Mają imitować natywną funkcjonalność przeglądarki. Worklet wywołujemy specjalną funkcją addModule
, która jest obietnicą.
await demoWorklet.addModule("path/to/script.js");
Promise.all([
demoWorklet1.addModule("script1.js"),
demoWorklet2.addModule("script2.js"),
]).then((results) => {...});
Okej to tyle odnośnie Workletów, przejdźmy do Paint API!
Dzięki Paint API możemy rysować, za pomocą context
(tak, to ten context z canvasa ), bezpośrednio do właściwości elementu takich jak background-image
. Jeżeli znacie canvasa
będziecie czuli się jak w domu.
Tworzymy Worklet!
registerPaint(
'paintWorketExample',
class {
static get inputProperties() {
return ['--myVariable'];
}
static get inputArguments() {
return ['<color>'];
}
static get contextOptions() {
return { alpha: true };
}
paint(ctx, size, properties, args) {
/* ... */
}
},
);
inputProperties
- tablicacustom properties
, których Worklet ma śledzić<color>
- tablica argumentów, jakie mogą być podane podczas wywołaniacontextOptions
- pozwala nam ustawićaplpę
, czyli takieopacity
dla kolorów, jeśli wartość będzie false, wszystkie kolory będą miały 100%opacity
paint
- tutaj dzieję się cała magia, funkcja może przyjąć kilka parametrów,ctx
jest praktycznie tym samym jakctx
w canvasie,size
jest obiektem składającym się z rozmiarów elementuwidth
iheight
,properties
są definiowane przezinputProperties
, a args przezinputArguments
.
Rejestracja Workleta w główym pliku .js
:
CSS.paintWorklet.addModule('ścieżka_do_workleta.js');
Użycie w CSS:
.exampleElement {
background-image: paint(paintWorketExample, blue);
}
Wywołujemy tutaj wcześniej napisaną funkcję paint()
, argument paintWorketExample
to nazwa Workletu, a blue
to podane argumenty.
Pełny kod tego przykładu ☝️ możecie znaleźć na Githubie GoogleChromeLabs
Animation API
Ten Worklet pozwala nam nasłuchiwać na przeróżne eventy takie jak scroll
,hover
czy click
. Dodatkowo, wpływa bardzo dobrze na wydajność(w porównaniu chociażby do requestAnimationFrame
) ponieważ działa na osobnym wątku.
registerAnimator(
'animationWorkletExample',
class {
constructor(options) {
/* ... */
}
animate(currentTime, effect) {
/* ... */
}
},
);
constructor
- ustawiamy tutaj setup naszego Workleta, odpalany przy stworzeniu nowej instancjianimate
- tutaj trafia cała logika,currentTime
to aktualna wartość czasu dla animacji,effect
jest tablicą efektów, których używa animacja
Wywołanie Workleta w głowyn pliku .js
:
async function init() {
await CSS.animationWorklet.addModule('ścieżka_do_workleta.js');
const effect = new KeyframeEffect(
document.querySelector('#rotation'),
[
{
transform: 'rotateZ(0deg) ',
},
{
transform: 'rotateZ(-280deg)',
},
],
{
duration: 3000,
iterations: 5,
},
);
new WorkletAnimation('animationWorkletExample', effect, document.timeline, {}).play();
}
init();
Wyjaśnijmy sobie powyższy przykład, tak jak zawsze, na początku pobieramy nasz Worklet, następnie inicjujemy klasę WorkletAnimation
. A w niej pojawia się nasz efekt - KeyframeEffect
. Pierwszy podajemy element, który ma być animowany. Następnie podajemy tablicę obiektów z keyframesami
i dodatkowe opcje takie jak: czas trwania animacji i liczba iteracji. Później już tylko oś czasu currentTime
i dodatkowe opcje dla konstruktora.
Pełny kod tego przykładu ☝️ możecie znaleźć na Githubie houdini-examples
Layout API
Ostatni już w Workletów, rozszerza nam możliwości jakie daje nam przeglądarka w ramach tworzenia layoutu. Dzięki temu możemy stworzyć własną wartość dla display
, na przykład masonry
.
registerLayout(
'exampleLayout',
class {
static get inputProperties() {
return ['--exampleVariable'];
}
static get childrenInputProperties() {
return ['--exampleChildVariable'];
}
static get layoutOptions() {
return {
childDisplay: 'normal',
sizing: 'block-like',
};
}
intrinsicSizes(children, edges, styleMap) {
/* ... */
}
layout(children, edges, constraints, styleMap, breakToken) {
/* ... */
}
},
);
inputProperties
- tak jak w przypadku Paint API, tylko w tym przypadku Worklet śledzicustom properties
, które przynależą do rodzica elementuchildrenInputProperties
- podobnie doinputProperties
, tym razem śledzimycustom properties
dla dzieci elementulayoutOptions
- znajdziemy tutajchildDisplay
, który definiuje w jaki sposób mają zostać wyświetlone dzieci elementu, jakoblock
(blokowo), czy teżnormal
(inlinowo). Za tosizing
może mieć pre-definiowane wartośćblock-like
lubmanual
. Mówi to przeglądarce czy ma przekalkulować rozmiar elementu, czy też nie.intrinsicSizes
- definiuje w jaki sposób kontener lub jego dzieci mają się zachowywać w kontekście layoutu.layout
- główna funkcja do tworzenia naszego layoutu, możemy wykorzystać tutaj dzieci elementu, ustawić krawędzie i ograniczenia.
Odpalenie Workleta w głównym pliku .js
:
async function init() {
await CSS.animationWorklet.addModule('ścieżka_do_workleta.js');
}
init();
Wykorzystanie w CSS:
.exampleElement {
display: layout(exampleLayout);
}
exampleLayout
- nazwa Workletu
Pełny kod tego przykładu ☝️ możecie znaleźć na Githubie houdini-examples
Wsparcie przeglądarek
Wsparcie przeglądarek dla CSS Houdini jest w tym momencie raczej średnie. Ale jestem pewien, że w najbliżej przyszłości sytuacja znacznie się poprawiać.
Warto jednak dać Houdini szansę i wykorzystywać tą technologię w nurcie Progressive Enhancement.
Jeżeli chcecie na bieżąco śledzić wsparcie dla CSS Houdini, to polecam Wam rzucić okiem na ishoudinireadyyet 👇
Przyszłość CSS czy niepotrzebna nowość?
Dla mnie jest to zdecydowanie przyszłość CSSa. Mamy dostęp do kilku świetnych API, możemy dodawać nowe funkcjonalności dla naszych styli, jednym słowem Houdini to przyszłość!
Poza tym, przez fakt, że możemy wpinać się do procesu renderowania, nasze strony i aplikacje stają się szybsze i wydajniejsze.
Podsumowanie
Jak się Wam podoba CSS Houdini? Dajcie znać 👇
Nie jest to na pewno łatwe rozwiązanie, największe problemy może sprawiać Layout API, jednak warto się pobawić, chociażby w tym playgroudzie.
Zostawiam, jak zawsze, przydatne linki i źródła, tym samym zachęcam do głębszego poznawania Houdini!
Do usłyszenia!