Praca z nowym obiektem typu CSS

TL;DR

CSS ma teraz odpowiedni, oparty na obiektach interfejs API przeznaczony do pracy z wartościami w JavaScript.

el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'

Skończyły się czasy łączenia ciągów znaków i drobnych błędów.

Wstęp

Stary CSSOM

Usługi porównywania cen od wielu lat mają model obiektowy (CSSOM). Za każdym razem, gdy odczytuje/ustawiasz element .style w kodzie JavaScript, używasz go:

// Element styles.
el.style.opacity = 0.3;
typeof el.style.opacity === 'string' // Ugh. A string!?

// Stylesheet rules.
document.styleSheets[0].cssRules[0].style.opacity = 0.3;

Nowy OM wpisany w CSS

Nowy model typu CSS typed (wpisany OM) należący do zespołu Houdini poszerza ten wizja świata przez dodawanie do wartości CSS typów, metod i właściwego modelu obiektowego. Zamiast ciągów tekstowych wartości są ujawniane jako obiekty JavaScript, co ułatwia sprawną (i rozsądną) manipulację kodem CSS.

Zamiast używać element.style, będziesz mieć dostęp do stylów za pomocą nowej właściwości .attributeStyleMap elementów i właściwości .styleMap dla reguł arkusza stylów. Oba zwracają obiekt StylePropertyMap.

// Element styles.
el.attributeStyleMap.set('opacity', 0.3);
typeof el.attributeStyleMap.get('opacity').value === 'number' // Yay, a number!

// Stylesheet rules.
const stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].styleMap.set('background', 'blue');

Obiekty StylePropertyMap są obiektami podobnymi do mapy, dlatego obsługują wszystkie typowe potencjalne wartości (get/set/keys/values/entries), dzięki czemu można je dostosowywać do własnych potrzeb:

// All 3 of these are equivalent:
el.attributeStyleMap.set('opacity', 0.3);
el.attributeStyleMap.set('opacity', '0.3');
el.attributeStyleMap.set('opacity', CSS.number(0.3)); // see next section
// el.attributeStyleMap.get('opacity').value === 0.3

// StylePropertyMaps are iterable.
for (const [prop, val] of el.attributeStyleMap) {
  console.log(prop, val.value);
}
// → opacity, 0.3

el.attributeStyleMap.has('opacity') // true

el.attributeStyleMap.delete('opacity') // remove opacity.

el.attributeStyleMap.clear(); // remove all styles.

W drugim przykładzie pole opacity jest ustawione na ciąg znaków ('0.3'), ale podczas późniejszego odczytu właściwości zwracana jest liczba.

Zalety

Jakie problemy próbuje rozwiązać OMW? Po przyjrzeniu się powyższych przykładom (i pozostałej części tego artykułu) można uznać, że OM w modelu CSS jest znacznie bardziej szczegółowy niż w starym modelu obiektowym. Zgadzam się!

Zanim odpiszesz w zapisie typu OM, weź pod uwagę kilka kluczowych funkcji zawartych w tabeli:

  • Mniej błędów, na przykład wartości liczbowe są zawsze zwracane jako liczby, a nie ciągi tekstowe.

    el.style.opacity += 0.1;
    el.style.opacity === '0.30.1' // dragons!
    
  • Operacje arytmetyczne i przeliczanie jednostek – przekształcaj jednostki długości bezwzględnej (np. px -> cm) i przeprowadzaj podstawowe obliczenia matematyczne.

  • Zmniejszanie i zaokrąglanie wartości. Wpisane wartości zaokrągleń lub zacisków w OM, aby były w akceptowalnym zakresie dla danej usługi.

  • Większa skuteczność. Przeglądarka musi mniej poświęcać pracy na szeregowanie i deserializację wartości ciągów. Obecnie wyszukiwarka podobnie rozumie wartości CSS w JS i C++. Tab Akins zaobserwował testy porównawcze z wcześniejszymi wynikami, dzięki którym w przypadku operacji na sekundę wpisywanych OM jest około 30% szybciej niż w starym CSSOM i ciągach tekstowych. Może to być istotne w przypadku szybkich animacji CSS korzystających z metody requestionAnimationFrame(). Strona crbug.com/808933 śledzi dodatkowe prace związane z wydajnością w Blink.

  • Obsługa błędów. Nowe metody analizy wprowadzają obsługę błędów w świecie CSS.

  • „Czy mam użyć nazw CSS czy ciągów tekstowych zapisanych wielbłądami?” Nie musisz już zgadywać, czy nazwy mają format wielbłąda czy ciągi znaków (np.el.style.backgroundColor czy el.style['background-color']). Nazwy właściwości CSS w wpisanym operatorze są zawsze ciągami znaków, które pasują do tekstu wpisanego w CSS.

Obsługa przeglądarek i wykrywanie funkcji

Wpisana przez Ciebie operacja trafiła do Chrome 66 i jest wdrażana w Firefoksie. Edge doświadczył oznak wsparcia, ale nie został jeszcze dodany do panelu platformy.

W przypadku wykrywania cech możesz sprawdzić, czy zdefiniowano jedną z fabryk liczbowych CSS.*:

if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}

Podstawy interfejsu API

Uzyskiwanie dostępu do stylów

Wartości różnią się od jednostek w OMary typu CSS. Pobranie stylu zwraca element CSSUnitValue zawierający value i unit:

el.attributeStyleMap.set('margin-top', CSS.px(10));
// el.attributeStyleMap.set('margin-top', '10px'); // string arg also works.
el.attributeStyleMap.get('margin-top').value  // 10
el.attributeStyleMap.get('margin-top').unit // 'px'

// Use CSSKeyWorldValue for plain text values:
el.attributeStyleMap.set('display', new CSSKeywordValue('initial'));
el.attributeStyleMap.get('display').value // 'initial'
el.attributeStyleMap.get('display').unit // undefined

Style wynikowe

HTMLElement Obliczone style zostały przeniesione z interfejsu API window do nowej metody computedStyleMap():

Stary CSSOM

el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!

New Typed OM

el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5

Zmniejszanie / zaokrąglanie wartości

Jedną z przydatnych funkcji nowego modelu obiektowego jest automatyczne zaciskanie lub zaokrąglanie obliczonych wartości stylu. Załóżmy na przykład, że próbujesz ustawić opacity na wartość spoza akceptowanego zakresu, [0, 1]. Wpisanie OM ogranicza wartość do 1 podczas obliczania stylu:

el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3  // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.

Podobnie ustawienie z-index:15.4 zaokrągla do 15, więc wartość pozostaje liczbą całkowitą.

el.attributeStyleMap.set('z-index', CSS.number(15.4));
el.attributeStyleMap.get('z-index').value  === 15.4 // val not rounded.
el.computedStyleMap().get('z-index').value === 15   // computed style is rounded.

Wartości liczbowe CSS

Liczby są reprezentowane przez 2 typy obiektów CSSNumericValue w modelu OM:

  1. CSSUnitValue – wartości zawierające jeden typ jednostki (np. "42px").
  2. CSSMathValue – wartości, które zawierają więcej niż 1 wartość/jednostkę, np.wyrażenie matematyczne (np. "calc(56em + 10%)").

Wartości jednostkowe

Proste wartości liczbowe ("50%") są reprezentowane przez obiekty CSSUnitValue. Możesz utworzyć te obiekty bezpośrednio (new CSSUnitValue(10, 'px')), jednak w większości przypadków będziesz korzystać z metod fabryki CSS.*:

const {value, unit} = CSS.number('10');
// value === 10, unit === 'number'

const {value, unit} = CSS.px(42);
// value === 42, unit === 'px'

const {value, unit} = CSS.vw('100');
// value === 100, unit === 'vw'

const {value, unit} = CSS.percent('10');
// value === 10, unit === 'percent'

const {value, unit} = CSS.deg(45);
// value === 45, unit === 'deg'

const {value, unit} = CSS.ms(300);
// value === 300, unit === 'ms'

W specyfikacji znajdziesz pełną listę metod CSS.*.

Wartości matematyczne

Obiekty CSSMathValue reprezentują wyrażenia matematyczne i zwykle zawierają więcej niż 1 wartość/jednostkę. Typowym przykładem jest utworzenie wyrażenia CSS calc(), ale istnieją metody w przypadku wszystkich funkcji CSS: calc(), min() i max().

new CSSMathSum(CSS.vw(100), CSS.px(-10)).toString(); // "calc(100vw + -10px)"

new CSSMathNegate(CSS.px(42)).toString() // "calc(-42px)"

new CSSMathInvert(CSS.s(10)).toString() // "calc(1 / 10s)"

new CSSMathProduct(CSS.deg(90), CSS.number(Math.PI/180)).toString();
// "calc(90deg * 0.0174533)"

new CSSMathMin(CSS.percent(80), CSS.px(12)).toString(); // "min(80%, 12px)"

new CSSMathMax(CSS.percent(80), CSS.px(12)).toString(); // "max(80%, 12px)"

Wyrażenia zagnieżdżone

Korzystanie z funkcji matematycznych do tworzenia bardziej złożonych wartości staje się nieco mylące. Poniżej znajdziesz kilka przykładów, od których możesz zacząć. Dodaję dodatkowe wcięcie, aby były bardziej czytelne.

Element calc(1px - 2 * 3em) zostałby utworzony w następujący sposób:

new CSSMathSum(
  CSS.px(1),
  new CSSMathNegate(
    new CSSMathProduct(2, CSS.em(3))
  )
);

Element calc(1px + 2px + 3px) zostałby utworzony w następujący sposób:

new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));

Element calc(calc(1px + 2px) + 3px) zostałby utworzony w następujący sposób:

new CSSMathSum(
  new CSSMathSum(CSS.px(1), CSS.px(2)),
  CSS.px(3)
);

Operacje arytmetyczne

Jedną z najbardziej przydatnych funkcji OM typu CSS Typed jest możliwość wykonywania operacji matematycznych na obiektach CSSUnitValue.

Operacje podstawowe

Obsługiwane są operacje podstawowe (add/sub/mul/div/min/max):

CSS.deg(45).mul(2) // {value: 90, unit: "deg"}

CSS.percent(50).max(CSS.vw(50)).toString() // "max(50%, 50vw)"

// Can Pass CSSUnitValue:
CSS.px(1).add(CSS.px(2)) // {value: 3, unit: "px"}

// multiple values:
CSS.s(1).sub(CSS.ms(200), CSS.ms(300)).toString() // "calc(1s + -200ms + -300ms)"

// or pass a `CSSMathSum`:
const sum = new CSSMathSum(CSS.percent(100), CSS.px(20)));
CSS.vw(100).add(sum).toString() // "calc(100vw + (100% + 20px))"

Konwersja

Jednostki długości bezwzględnej można przekonwertować na inne długości:

// Convert px to other absolute/physical lengths.
el.attributeStyleMap.set('width', '500px');
const width = el.attributeStyleMap.get('width');
width.to('mm'); // CSSUnitValue {value: 132.29166666666669, unit: "mm"}
width.to('cm'); // CSSUnitValue {value: 13.229166666666668, unit: "cm"}
width.to('in'); // CSSUnitValue {value: 5.208333333333333, unit: "in"}

CSS.deg(200).to('rad').value // 3.49066...
CSS.s(2).to('ms').value // 2000

Equality

const width = CSS.px(200);
CSS.px(200).equals(width) // true

const rads = CSS.deg(180).to('rad');
CSS.deg(180).equals(rads.to('deg')) // true

Wartości przekształcenia CSS

Przekształcenia CSS tworzy się za pomocą właściwości CSSTransformValue i przekazującej tablicę wartości przekształceń (np. CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). Załóżmy na przykład, że chcesz odtworzyć ten kod CSS:

transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);

Przetłumaczone na wpisane przez OM:

const transform =  new CSSTransformValue([
  new CSSRotate(CSS.deg(45)),
  new CSSScale(CSS.number(0.5), CSS.number(0.5)),
  new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10))
]);

Poza szczegółowość (lolz!) CSSTransformValue ma kilka ciekawych funkcji. Zawiera właściwość logiczną, która pozwala rozróżniać przekształcenia 2D i 3D, oraz metodę .toMatrix(), która zwraca reprezentację przekształcenia DOMMatrix:

new CSSTranslate(CSS.px(10), CSS.px(10)).is2D // true
new CSSTranslate(CSS.px(10), CSS.px(10), CSS.px(10)).is2D // false
new CSSTranslate(CSS.px(10), CSS.px(10)).toMatrix() // DOMMatrix

Przykład: animowanie kostki

Przyjrzyjmy się praktycznym przykładom stosowania przekształceń. Do animowania sześcianu użyjemy JavaScriptu i przekształceń CSS.

const rotate = new CSSRotate(0, 0, 1, CSS.deg(0));
const transform = new CSSTransformValue([rotate]);

const box = document.querySelector('#box');
box.attributeStyleMap.set('transform', transform);

(function draw() {
  requestAnimationFrame(draw);
  transform[0].angle.value += 5; // Update the transform's angle.
  // rotate.angle.value += 5; // Or, update the CSSRotate object directly.
  box.attributeStyleMap.set('transform', transform); // commit it.
})();

Uwaga:

  1. Wartości liczbowe oznaczają, że możemy bezpośrednio zwiększać kąt za pomocą obliczeń matematycznych.
  2. Zamiast docierać do DOM lub odczytywać wartości w każdej klatce (np. brak box.style.transform=`rotate(0,0,1,${newAngle}deg)`), animacja jest wykonywana przez aktualizację bazowego obiektu danych CSSTransformValue w celu poprawy wydajności.

Wersja demonstracyjna

Jeśli Twoja przeglądarka obsługuje typowy OM, poniżej będzie widoczna czerwona kostka. Gdy najedziesz kursorem myszy, kostka zacznie się obracać. Animacja jest oparta na CSS Typed OM! 🤘

Wartości niestandardowych właściwości CSS

Kod CSS var() stanie się obiektem CSSVariableReferenceValue w modelu OM Typed. Ich wartości są analizowane w funkcji CSSUnparsedValue, ponieważ mogą przyjmować dowolny typ (px, %, em, rgba() itp.).

const foo = new CSSVariableReferenceValue('--foo');
// foo.variable === '--foo'

// Fallback values:
const padding = new CSSVariableReferenceValue(
    '--default-padding', new CSSUnparsedValue(['8px']));
// padding.variable === '--default-padding'
// padding.fallback instanceof CSSUnparsedValue === true
// padding.fallback[0] === '8px'

Aby uzyskać wartość właściwości niestandardowej, musisz wykonać kilka czynności:

<style>
  body {
    --foo: 10px;
  }
</style>
<script>
  const styles = document.querySelector('style');
  const foo = styles.sheet.cssRules[0].styleMap.get('--foo').trim();
  console.log(CSSNumericValue.parse(foo).value); // 10
</script>

Wartości pozycji

Właściwości CSS, które zajmują pozycję x/y rozdzielone spacjami, np. object-position, są reprezentowane przez obiekty CSSPositionValue.

const position = new CSSPositionValue(CSS.px(5), CSS.px(10));
el.attributeStyleMap.set('object-position', position);

console.log(position.x.value, position.y.value);
// → 5, 10

Analizowanie wartości

Typed OM wprowadza metody analizy na platformie internetowej. Oznacza to, że wreszcie można analizować wartości CSS w sposób zautomatyzowany, zanim spróbujesz je wykorzystać. Ta nowa funkcja może pomóc w wyłapaniu wczesnych błędów i nieprawidłowego kodu CSS.

Analizuj cały styl:

const css = CSSStyleValue.parse(
    'transform', 'translate3d(10px,10px,0) scale(0.5)');
// → css instanceof CSSTransformValue === true
// → css.toString() === 'translate3d(10px, 10px, 0) scale(0.5)'

Analizuj wartości na format CSSUnitValue:

CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}

// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'

Obsługa błędów

Przykład – sprawdź, czy parser CSS będzie zadowolony z tej wartości transform:

try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}

Podsumowanie

Dobrze w końcu mieć zaktualizowany model obiektowy dla CSS. Praca z tekstami smyczkowymi nigdy nie była dla mnie najlepsza. Interfejs CSS Typed OM API jest dość szczegółowy, ale mamy nadzieję, że dzięki temu będzie na nim mniej błędów i wydajniejszy kod.