TL;DR
يتضمّن CSS الآن واجهة برمجة تطبيقات مناسبة مستندة إلى العناصر للتعامل مع القيم في 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;
New CSS Typed OM
توسّع 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) وإجراء عمليات حسابية أساسية.
- تحديد الحدّ الأدنى والأقصى للقيمة وتقريبها: تعمل عمليات التقريب و/أو التثبيت في Typed OM على تعديل القيم لتكون ضمن النطاقات المقبولة للسمة. 
- أداء أفضل: سيضطر المتصفّح إلى إجراء عمليات أقل لتسلسل القيم النصية وإلغاء تسلسلها. أصبح المحرّك الآن يستخدم فهمًا مشابهًا لقيم CSS في كل من JavaScript وC++. وقد عرض Tab Atkins بعض مقاييس الأداء المبكرة التي تشير إلى أنّ Typed OM أسرع بنسبة% 30 تقريبًا في العمليات في الثانية مقارنةً باستخدام CSSOM القديم والسلاسل. يمكن أن يكون ذلك مهمًا للحركات السريعة في CSS باستخدام - requestionAnimationFrame(). يتتبّع الرابط crbug.com/808933 التحسينات الإضافية على الأداء في Blink.
- معالجة الأخطاء: توفّر طرق تحليل جديدة معالجة الأخطاء في عالم CSS. 
- "هل يجب استخدام أسماء أو سلاسل CSS مكتوبة بنمط CamelCase؟" لن تحتاج بعد الآن إلى التخمين بشأن ما إذا كانت الأسماء مكتوبة بنظام الكتابة المختلطة أو كسلاسل (مثل - el.style.backgroundColorمقابل- el.style['background-color'])، لأنّ أسماء خصائص CSS في Typed OM تكون دائمًا سلاسل، ما يطابق ما تكتبه فعليًا في CSS :)
المتصفّحات المتوافقة ورصد الميزات
تم إطلاق Typed OM في الإصدار 66 من Chrome، ويتم تنفيذه حاليًا في Firefox. أظهر متصفّح Edge مؤشرات على توفير هذه الميزة، ولكن لم تتم إضافتها بعد إلى لوحة بيانات المنصة.
بالنسبة إلى رصد الميزات، يمكنك التحقّق مما إذا تم تحديد أحد CSS.* المصانع الرقمية
التالية:
if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}
أساسيات واجهة برمجة التطبيقات
الوصول إلى الأنماط
يتم فصل القيم عن الوحدات في 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
الأنماط المحتسَبة
تم نقل الأنماط المحسوبة من واجهة برمجة تطبيقات على 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);
تمت الترجمة إلى "الردّ المكتوب":
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. لم أشعر يومًا بالراحة عند التعامل مع السلاسل. تتضمّن واجهة CSS Typed OM API بعض التفاصيل، ولكن نأمل أن يؤدي ذلك إلى تقليل الأخطاء وتحسين أداء الرمز البرمجي في المستقبل.