אמ;לק
ל-CSS יש עכשיו API מבוסס-אובייקטים שמאפשר לעבוד עם ערכים ב-JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
הימים שבהם שרשרתם מחרוזות והתמודדתם עם באגים קטנים נגמרו!
מבוא
CSSOM מדור קודם
ל-CSS יש מודל אובייקטים (CSSOM) כבר הרבה שנים. למעשה, בכל פעם שקוראים או מגדירים את .style ב-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;
CSS Typed OM חדש
מודל אובייקטים מוקלד של CSS (Typed OM) החדש, שהוא חלק מפרויקט Houdini, מרחיב את התפיסה הזו על ידי הוספת סוגים, שיטות ומודל אובייקטים מתאים לערכי CSS. במקום מחרוזות, הערכים מוצגים כאובייקטים של JavaScript כדי לאפשר שינוי יעיל (והגיוני) של CSS.
במקום להשתמש ב-element.style, תוכלו לגשת לסגנונות באמצעות מאפיין .attributeStyleMap חדש לרכיבים ומאפיין .styleMap לכללים של גיליונות סגנונות. שניהם מחזירים אובייקט 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');
מכיוון ש-StylePropertyMap הם אובייקטים דמויי Map, הם תומכים בכל הפונקציות הרגילות (get/set/keys/values/entries), ולכן קל לעבוד איתם:
// 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.
שימו לב שבמקרה השני, opacity מוגדר כמחרוזת ('0.3'), אבל כשקוראים את המאפיין מאוחר יותר, מקבלים מספר.
יתרונות
אז אילו בעיות מנסה לפתור CSS Typed OM? אם מסתכלים על הדוגמאות שלמעלה (ובשאר המאמר), אפשר לטעון ש-CSS Typed OM הוא מפורט הרבה יותר ממודל האובייקט הישן. אני מסכים/ה!
לפני שפוסלים את Typed OM, כדאי לעיין בכמה מהתכונות העיקריות שלו:
- פחות באגים. לדוגמה, ערכים מספריים תמיד מוחזרים כמספרים ולא כמחרוזות. - el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
- פעולות אריתמטיות והמרת יחידות. אפשר להמיר בין יחידות אורך מוחלטות (למשל, - px->- cm) ולבצע חישובים מתמטיים בסיסיים.
- הגבלת ערכים ועיגול. הפונקציה Typed OM מעגלת ו/או מגבילה ערכים כדי שהם יהיו בטווחים הקבילים של מאפיין. 
- ביצועים טובים יותר. הדפדפן צריך לבצע פחות עבודה בסדרת הפעולות של המרת ערכי מחרוזות לפורמט שניתן להעברה (serialization) והמרתם בחזרה לפורמט המקורי (deserialization). עכשיו המנוע משתמש בהבנה דומה של ערכי CSS ב-JS וב-C++. טאב אקינס הציג כמה מדדי ביצועים מוקדמים שמראים ש-Typed OM מהיר יותר ב-כ-30% בפעולות לשנייה בהשוואה לשימוש ב-CSSOM ובמחרוזות הישנים. השיפור הזה יכול להיות משמעותי עבור אנימציות CSS מהירות באמצעות - requestionAnimationFrame(). בכתובת crbug.com/808933 אפשר לעקוב אחרי עבודת ביצועים נוספת ב-Blink.
- טיפול בשגיאות. שיטות ניתוח חדשות מביאות טיפול בשגיאות לעולם ה-CSS. 
- "כדאי להשתמש בשמות או במחרוזות CSS בפורמט CamelCase?" לא צריך יותר לנחש אם השמות הם בפורמט CamelCase או מחרוזות (למשל - el.style.backgroundColorלעומת- el.style['background-color']). שמות המאפיינים של CSS ב-Typed OM הם תמיד מחרוזות, כמו מה שכותבים בפועל ב-CSS :)
תמיכה בדפדפן וזיהוי תכונות
ה-OM המוקלד הגיע ל-Chrome 66 ומיושם ב-Firefox. ב-Edge יש סימנים לתמיכה, אבל עדיין לא נוספה תמיכה ב-לוח הבקרה של הפלטפורמה.
כדי לזהות תכונות, אפשר לבדוק אם אחת מהתכונות הבאות מוגדרת: CSS.*
if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}
יסודות ה-API
גישה לסגנונות
הערכים מופרדים מהיחידות ב-CSS Typed OM. קבלת סגנון מחזירה את CSSUnitValue שמכיל את value ואת 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
סגנונות מחושבים
Computed styles
הועברו מ-API ב-window לשיטה חדשה ב-HTMLElement,
computedStyleMap():
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
תחימה או עיגול של ערכים
אחד מהיתרונות של מודל האובייקטים החדש הוא ההצמדה האוטומטית ו/או העיגול של ערכי הסגנון המחושבים. לדוגמה, נניח שמנסים להגדיר את opacity לערך מחוץ לטווח המקובל, [0, 1]. הגדרת OM עם הקלדה מגבילה את הערך ל-1 כשמחשבים את הסגנון:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3  // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
באופן דומה, אם מגדירים את z-index:15.4 ל-15, הערך נשאר מספר שלם.
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.
ערכים מספריים ב-CSS
מספרים מיוצגים על ידי שני סוגים של אובייקטים מסוג CSSNumericValue ב-Typed OM:
- CSSUnitValue– ערכים שמכילים סוג יחידה אחד (למשל,"42px").
- CSSMathValue– ערכים שמכילים יותר מערך או יחידה אחת, כמו ביטוי מתמטי (לדוגמה,"calc(56em + 10%)").
ערכי יחידות
ערכים מספריים פשוטים ("50%") מיוצגים על ידי אובייקטים מסוג CSSUnitValue.
אפשר ליצור את האובייקטים האלה ישירות (new CSSUnitValue(10, 'px')), אבל ברוב המקרים משתמשים בשיטות ליצירת אובייקטים מסוג 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'
הרשימה המלאה של שיטות CSS.* מופיעה במפרט.
ערכים מתמטיים
אובייקטים של CSSMathValue מייצגים ביטויים מתמטיים ובדרך כלל מכילים יותר מערך אחד או יחידה אחת. הדוגמה הנפוצה היא יצירת ביטוי CSS calc(), אבל יש שיטות לכל הפונקציות של 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)"
ביטויים מקוננים
השימוש בפונקציות המתמטיות כדי ליצור ערכים מורכבים יותר עלול להיות מבלבל. בהמשך מפורטות כמה דוגמאות שיעזרו לכם להתחיל. הוספתי הזחה נוספת כדי שיהיה קל יותר לקרוא אותם.
המחרוזת calc(1px - 2 * 3em) תהיה בנויה כך:
new CSSMathSum(
  CSS.px(1),
  new CSSMathNegate(
    new CSSMathProduct(2, CSS.em(3))
  )
);
המחרוזת calc(1px + 2px + 3px) תהיה בנויה כך:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
המחרוזת calc(calc(1px + 2px) + 3px) תהיה בנויה כך:
new CSSMathSum(
  new CSSMathSum(CSS.px(1), CSS.px(2)),
  CSS.px(3)
);
פעולות חשבוניות
אחת התכונות הכי שימושיות של CSS Typed OM היא האפשרות לבצע פעולות מתמטיות על אובייקטים של CSSUnitValue.
פעולות בסיסיות
נתמכות פעולות בסיסיות (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))"
המרה
יחידות אורך מוחלטות אפשר להמיר ליחידות אורך אחרות:
// 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
ערכי טרנספורמציה של CSS
טרנספורמציות CSS נוצרות באמצעות CSSTransformValue והעברת מערך של ערכי טרנספורמציה (לדוגמה, CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). לדוגמה, נניח שרוצים ליצור מחדש את ה-CSS הזה:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
תורגם ל-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))
]);
בנוסף למידע המפורט (lolz!), לCSSTransformValue יש כמה תכונות מגניבות. יש לו מאפיין בוליאני להבחנה בין טרנספורמציות דו-ממדיות ותלת-ממדיות, ושיטת .toMatrix() להחזרת הייצוג 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
דוגמה: אנימציה של קובייה
עכשיו נראה דוגמה מעשית לשימוש בטרנספורמציות. נשתמש ב-JavaScript ובטרנספורמציות של 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.
})();
שימו לב:
- ערכים מספריים מאפשרים לנו להגדיל את הזווית ישירות באמצעות מתמטיקה.
- במקום לשנות את ה-DOM או לקרוא ערך בכל פריים (למשל, לא box.style.transform=`rotate(0,0,1,${newAngle}deg)`), האנימציה מופעלת על ידי עדכון של אובייקט הנתונים הבסיסיCSSTransformValue, מה שמשפר את הביצועים.
הדגמה (דמו)
אם הדפדפן שלכם תומך ב-Typed OM, יופיע מתחתיו קובייה אדומה. הקוביה מתחילה להסתובב כשמעבירים מעליה את העכבר. האנימציה מופעלת על ידי CSS Typed OM! 🤘
ערכים של מאפיינים מותאמים אישית של CSS
CSS var() הופך לאובייקט CSSVariableReferenceValue ב-Typed OM.
הערכים שלהם מנותחים ל-CSSUnparsedValue כי הם יכולים לקבל כל סוג (px, %, em, rgba() וכו').
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'
אם רוצים לקבל את הערך של מאפיין מותאם אישית, צריך לבצע כמה פעולות:
<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>
ערכי מיקום
מאפייני CSS שמקבלים מיקום x/y מופרד ברווח, כמו object-position, מיוצגים על ידי אובייקטים של 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
ניתוח ערכים
ה-Typed OM מציג שיטות ניתוח לפלטפורמת האינטרנט! המשמעות היא שסוף סוף אפשר לנתח ערכי CSS באופן פרוגרמטי, לפני שמנסים להשתמש בהם! היכולת החדשה הזו יכולה לעזור לכם לזהות באגים מוקדמים ו-CSS לא תקין.
ניתוח של סגנון מלא:
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)'
ניתוח הערכים בטווח CSSUnitValue:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
טיפול בשגיאות
דוגמה – בדיקה אם מנתח ה-CSS יקבל את הערך transform:
try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}
סיכום
סוף סוף יש מודל אובייקטים מעודכן ל-CSS. העבודה עם מחרוזות תמיד הרגישה לי לא נכונה. ממשק ה-API של CSS Typed OM הוא קצת מפורט, אבל יש לקוות שהוא יגרום לפחות באגים ולקוד עם ביצועים טובים יותר בהמשך.