אמ;לק
עכשיו יש ל-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;
OM חדש מסוג CSS
ה-CSS Typed Object Model (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
הם אובייקטים דמויי-מפה, הם תומכים בכל
הסימנים הרגילים (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
) ולבצע פעולות מתמטיות בסיסיות.עיגול ועיגול של ערך. מוקלד/ה בעיגול או בעיגול של ערכים מסוג OM כדי שהם יהיו בטווחים הקבילים לנכס.
ביצועים טובים יותר. הדפדפן צריך פחות עבודה לגבי סידור של ערכי מחרוזת וביצוע פעולת deserialize. עכשיו, המנוע משתמש בהבנה דומה של ערכי CSS ב-JS וב-C++. חברת Tab Akins הציגה כמה אמות השוואה של ביצועים ראשוניים שמציבות את Typed OM כ-30% מהר יותר בפעולות לשנייה, בהשוואה לשימוש ב-CSSOM ובמחרוזות הישנים. זה יכול להיות משמעותי עבור אנימציות CSS מהירות באמצעות
requestionAnimationFrame()
. crbug.com/808933 עוקב אחר פעולות ביצועים נוספות ב-Blink.טיפול בשגיאות. שיטות ניתוח חדשות מאפשרות טיפול בשגיאות בעולם ה-CSS.
"האם כדאי להשתמש בשמות או במחרוזות CSS הכוללים אותיות גמלים?" לא צריך יותר לנחש אם השמות הם באותיות גמלים או מחרוזות (למשל
el.style.backgroundColor
נגדel.style['background-color']
). שמות של מאפייני CSS בסוג OM הם תמיד מחרוזות, שתואמים למה שנכתב בפועל ב-CSS :)
תמיכה בדפדפן וזיהוי תכונות
סוג OM נחתה ב-Chrome 66 ומיושם ב-Firefox. ב-Edge הציגה אותות של תמיכה, אבל עדיין לא הוסיפה אותה למרכז הבקרה של הפלטפורמה.
לצורך זיהוי תכונות, אפשר לבדוק אם אחד מהמפעלים המספריים של CSS.*
מוגדר:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
יסודות ה-API
גישה לסגנונות
הערכים נפרדים מהיחידות ב-CSS מסוג 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
סגנונות מחושבים
סגנונות מחושבים עברו מ-API ב-window
לשיטה חדשה בתאריך HTMLElement
, computedStyleMap()
:
CSSOM ישן
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
OM מסוג 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
אובייקטים מייצגים ביטויים מתמטיים ולרוב מכילים יותר מערך אחד/יחידה אחת. הדוגמה הנפוצה היא יצירת ביטוי calc()
של CSS, אבל יש שיטות לכל פונקציות ה-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 מסוג 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
יש כמה תכונות
מגניבות. יש לו מאפיין בוליאני כדי להבחין בין טרנספורמציות דו-ממדיות ותלת-ממדיות
ו-method .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
ב-Type 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. אף פעם לא נראה לי נכון לעבוד עם מיתרים. ה-CSS Typed OM API קצת ארוך, אבל אנחנו מקווים שהוא יגרום לפחות באגים ולביצועים טובים יותר בהמשך.