TL;DR
CSS ma teraz odpowiedni interfejs API oparty na obiektach 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'
Koniec z łączeniem ciągów znaków i subtelnymi błędami.
Wprowadzenie
Stary CSSOM
CSS ma model obiektowy (CSSOM) od wielu lat. W rzeczywistości używasz go za każdym razem, gdy odczytujesz lub ustawiasz wartość .style w JavaScript:
// 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 model obiektowy CSS
Nowy model obiektów z określonym typem CSS (Typed OM), który jest częścią projektu Houdini, rozszerza tę perspektywę, dodając typy, metody i odpowiedni model obiektów do wartości CSS. Zamiast ciągów znaków wartości są udostępniane jako obiekty JavaScript, co ułatwia wydajne (i rozsądne) manipulowanie CSS.
Zamiast element.style będziesz uzyskiwać dostęp do stylów za pomocą nowej właściwości .attributeStyleMap dla elementów i właściwości .styleMap dla reguł arkusza stylów. Obie 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');
Ponieważ obiekty StylePropertyMap są podobne do map, obsługują wszystkie standardowe metody (get/set/keys/values/entries), co sprawia, że są elastyczne w użyciu:
// 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.
Zwróć uwagę, że w drugim przykładzie wartość opacity jest ustawiona jako ciąg znaków ('0.3'), ale gdy później odczytasz właściwość, otrzymasz liczbę.
Zalety
Jakie problemy ma rozwiązać CSS Typed OM? Patrząc na powyższe przykłady (i na resztę tego artykułu), można stwierdzić, że CSS Typed OM jest znacznie bardziej rozbudowany niż stary model obiektowy. Zgadzam się!
Zanim odrzucisz Typed OM, zapoznaj się z jego najważniejszymi funkcjami:
- Mniej błędów, np. wartości liczbowe są zawsze zwracane jako liczby, a nie ciągi znaków. - el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
- Działania arytmetyczne i przeliczanie jednostek: przeliczanie jednostek długości bezwzględnej (np. - px–>- cm) i wykonywanie podstawowych działań matematycznych.
- Ograniczanie i zaokrąglanie wartości Wpisany OM zaokrągla lub ogranicza wartości, aby mieściły się w dopuszczalnych zakresach właściwości. 
- Zwiększona skuteczność Przeglądarka musi wykonać mniej pracy przy serializacji i deserializacji wartości ciągów znaków. Teraz silnik podobnie interpretuje wartości CSS w JS i C++. Tab Akins zaprezentował wstępne testy wydajności, które pokazują, że Typed OM jest o około 30% szybszy w operacjach na sekundę w porównaniu ze starym CSSOM i ciągami znaków. Może to mieć duże znaczenie w przypadku szybkich animacji CSS wykorzystujących - requestionAnimationFrame(). crbug.com/808933 zawiera informacje o dodatkowych pracach nad wydajnością w Blinku.
- Obsługa błędów Nowe metody analizowania wprowadzają obsługę błędów w świecie CSS. 
- „Czy mam używać nazw CSS w notacji camel-case czy ciągów znaków?” Nie musisz już zgadywać, czy nazwy są pisane wielbłądzią notacją, czy są ciągami znaków (np. - el.style.backgroundColorczy- el.style['background-color']). Nazwy właściwości CSS w Typed OM są zawsze ciągami znaków, co odpowiada temu, co faktycznie piszesz w CSS.
Obsługa przeglądarek i wykrywanie funkcji
Wpisane OM pojawiły się w Chrome 66 i są wdrażane w Firefoxie. Przeglądarka Edge wykazuje oznaki obsługi, ale nie dodała jeszcze tej funkcji do panelu platformy.
W przypadku wykrywania funkcji możesz sprawdzić, czy zdefiniowano jedną z tych fabryk wartości liczbowych: CSS.*.
if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}
Podstawy interfejsu API
Dostęp do stylów
W CSS Typed OM wartości są oddzielone od jednostek. Pobranie stylu zwraca obiekt 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
Obliczone style
zostały przeniesione z interfejsu API na window do nowej metody na HTMLElement,
computedStyleMap():
Stary CSSOM
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
Nowy typ OM
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Ograniczanie / zaokrąglanie wartości
Jedną z przydatnych funkcji nowego modelu obiektów jest automatyczne ograniczanie lub zaokrąglanie obliczonych wartości stylu. Załóżmy na przykład, że próbujesz ustawić wartość parametru
opacity na wartość spoza dopuszczalnego zakresu [0, 1]. Wpisany 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 się 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 CSSNumericValueobiektów w Typed OM:
- CSSUnitValue– wartości zawierające jeden typ jednostki (np.- "42px").
- CSSMathValue– wartości zawierające więcej niż 1 wartość lub jednostkę, np.wyrażenie matematyczne (np.- "calc(56em + 10%)").
Wartości jednostek
Proste wartości liczbowe ("50%") są reprezentowane przez obiekty CSSUnitValue.
Możesz bezpośrednio tworzyć te obiekty (new CSSUnitValue(10, 'px')), ale najczęściej będziesz używać metod fabrycznych 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'
Pełną listę metod CSS.* znajdziesz w specyfikacji.
Wartości matematyczne
CSSMathValue obiekty reprezentują wyrażenia matematyczne i zwykle zawierają więcej niż jedną wartość lub jednostkę. Typowym przykładem jest utworzenie wyrażenia CSS calc(), ale istnieją metody dla wszystkich funkcji CSS: calc(), min(), 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
Używanie funkcji matematycznych do tworzenia bardziej złożonych wartości może być nieco mylące. Poniżej znajdziesz kilka przykładów, które pomogą Ci zacząć. Dodałem dodatkowe wcięcia, aby były bardziej czytelne.
calc(1px - 2 * 3em) będzie wyglądać tak:
new CSSMathSum(
  CSS.px(1),
  new CSSMathNegate(
    new CSSMathProduct(2, CSS.em(3))
  )
);
calc(1px + 2px + 3px) będzie wyglądać tak:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px) będzie wyglądać tak:
new CSSMathSum(
  new CSSMathSum(CSS.px(1), CSS.px(2)),
  CSS.px(3)
);
Operacje arytmetyczne
Jedną z najbardziej przydatnych funkcji interfejsu CSS Typed OM jest możliwość wykonywania operacji matematycznych na obiektach CSSUnitValue.
Podstawowe operacje
Obsługiwane są podstawowe operacje (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 przeliczać na inne jednostki 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łceń CSS
Przekształcenia CSS są tworzone za pomocą funkcji CSSTransformValue i przekazywania tablicy wartości przekształceń (np. CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). Załóżmy, że chcesz odtworzyć ten kod CSS:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Przetłumaczono na wpisany 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))
]);
Oprócz tego, że jest bardzo rozbudowany (lolz!), CSSTransformValue ma kilka fajnych funkcji. Ma właściwość logiczną, która odróżnia przekształcenia 2D i 3D, oraz metodę .toMatrix(), która zwraca reprezentację DOMMatrix przekształcenia:
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 sześcianu
Przyjrzyjmy się praktycznemu przykładowi użycia przekształceń. Do animowania sześcianu będziemy używać 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.
})();
Pamiętaj, że:
- Wartości liczbowe oznaczają, że możemy bezpośrednio zwiększać kąt za pomocą działań matematycznych.
- Zamiast modyfikować DOM lub odczytywać wartość w każdej klatce (np. bez box.style.transform=`rotate(0,0,1,${newAngle}deg)`), animacja jest sterowana przez aktualizowanie bazowego obiektu danychCSSTransformValue, co zwiększa wydajność.
Prezentacja
Jeśli Twoja przeglądarka obsługuje Typed OM, zobaczysz poniżej czerwony sześcian. Kostka zaczyna się obracać, gdy najedziesz na nią kursorem. Animacja jest oparta na interfejsie CSS Typed OM. 🤘
Wartości właściwości niestandardowych CSS
CSS var() staje się obiektem CSSVariableReferenceValue w Typed OM.
Ich wartości są analizowane w 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'
Jeśli chcesz 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 przyjmują rozdzieloną spacją pozycję x/y, 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
Wpisany model obiektów wprowadza do platformy internetowej metody analizowania. Oznacza to, że możesz wreszcie programowo analizować wartości CSS zanim spróbujesz ich użyć. Ta nowa funkcja może uratować Ci życie, ponieważ pozwala wykrywać błędy i nieprawidłowo sformatowany kod CSS na wczesnym etapie.
Analizowanie pełnego stylu:
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)'
Przetwarzanie wartości w 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 zaakceptuje tę wartość transform:
try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}
Podsumowanie
Miło, że w końcu mamy zaktualizowany model obiektów CSS. Praca z ciągami znaków nigdy mi nie odpowiadała. Interfejs CSS Typed OM API jest nieco rozbudowany, ale miejmy nadzieję, że w przyszłości pozwoli to zmniejszyć liczbę błędów i zwiększyć wydajność kodu.