کار با CSS Typed Object Model جدید

TL;DR

CSS اکنون یک API مبتنی بر شیء مناسب برای کار با مقادیر در جاوا اسکریپت دارد.

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 در جاوا اسکریپت می‌خوانید/تنظیم می‌کنید، از آن استفاده می‌کنید:

// 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 OM

مدل شیء تایپ‌شده‌ی CSS جدید (Typed OM)، که بخشی از تلاش‌های Houdini است، این جهان‌بینی را با افزودن نوع‌ها، روش‌ها و یک مدل شیء مناسب به مقادیر CSS گسترش می‌دهد. به جای رشته‌ها، مقادیر به عنوان اشیاء جاوا اسکریپت در معرض نمایش قرار می‌گیرند تا دستکاری کارآمد (و معقول) CSS را تسهیل کنند.

به جای استفاده از element.style ، شما از طریق یک ویژگی جدید .attributeStyleMap برای عناصر و یک ویژگی .styleMap برای قوانین stylesheet به سبک‌ها دسترسی خواهید داشت. هر دو یک شیء 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 ) و انجام محاسبات اولیه .

  • مقادیر را با استفاده از گیره و کلمپ مشخص کنید . مقادیر را با استفاده از کلمپ و/یا کلمپ تایپ کنید تا در محدوده قابل قبول برای یک ملک قرار گیرند.

  • عملکرد بهتر . مرورگر باید کار کمتری برای سریال‌سازی و غیر سریال‌سازی مقادیر رشته انجام دهد. اکنون، موتور از درک مشابهی از مقادیر CSS در JS و C++ استفاده می‌کند. تب ایکینز برخی از معیارهای اولیه عملکرد را نشان داده است که Typed OM را در مقایسه با استفاده از CSSOM قدیمی و رشته‌ها، حدود 30٪ سریع‌تر در عملیات بر ثانیه قرار می‌دهد. این می‌تواند برای انیمیشن‌های سریع CSS با استفاده از requestionAnimationFrame() قابل توجه باشد. crbug.com/808933 کارهای عملکردی اضافی را در Blink دنبال می‌کند.

  • مدیریت خطا . روش‌های جدید تجزیه، مدیریت خطا را در دنیای CSS به ارمغان می‌آورند.

  • «آیا باید از نام‌های CSS با حروف بزرگ استفاده کنم یا رشته‌ها؟» دیگر نیازی به حدس زدن نیست که آیا نام‌ها با حروف بزرگ نوشته می‌شوند یا رشته‌ها (مثلاً el.style.backgroundColor در مقابل el.style['background-color'] ). نام‌های ویژگی‌های CSS در Typed OM همیشه رشته هستند و با آنچه واقعاً در CSS می‌نویسید مطابقت دارند :)

پشتیبانی مرورگر و تشخیص ویژگی

تایپ‌شده‌ی OM در کروم ۶۶ قرار گرفت و در فایرفاکس نیز در حال پیاده‌سازی است. اج نشانه‌هایی از پشتیبانی را نشان داده است، اما هنوز آن را به داشبورد پلتفرم خود اضافه نکرده است.

برای تشخیص ویژگی، می‌توانید بررسی کنید که آیا یکی از کارخانه‌های عددی 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

سبک‌های محاسبه‌شده

سبک‌های محاسباتی از یک API در window به یک متد جدید در HTMLElement به computedStyleMap() منتقل شده‌اند:

CSSOM قدیمی

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

تایپ جدید 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 نمایش داده می‌شوند:

  1. CSSUnitValue - مقادیری که شامل یک نوع واحد واحد هستند (مثلاً "42px" ).
  2. 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)
);

عملیات حسابی

یکی از مفیدترین ویژگی‌های The 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

برابری

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))
]);

علاوه بر طولانی بودنش (خنده!)، 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

مثال: متحرک‌سازی یک مکعب

بیایید یک مثال عملی از استفاده از transformها را ببینیم. ما از transformهای جاوا اسکریپت و 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.
})();

توجه داشته باشید که:

  1. مقادیر عددی به این معنی است که می‌توانیم زاویه را مستقیماً با استفاده از ریاضی افزایش دهیم!
  2. به جای دست زدن به DOM یا خواندن مجدد یک مقدار در هر فریم (مثلاً no box.style.transform=`rotate(0,0,1,${newAngle}deg)` )، انیمیشن با به‌روزرسانی شیء داده CSSTransformValue هدایت می‌شود و عملکرد را بهبود می‌بخشد .

نسخه آزمایشی

در زیر، اگر مرورگر شما از Typed OM پشتیبانی کند، یک مکعب قرمز خواهید دید. وقتی ماوس را روی آن قرار می‌دهید، مکعب شروع به چرخش می‌کند. این انیمیشن توسط CSS Typed OM پشتیبانی می‌شود! 🤘

مقادیر ویژگی‌های سفارشی CSS

تابع var() در Typed OM به یک شیء CSSVariableReferenceValue تبدیل می‌شود. مقادیر آنها به 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 داریم. کار با رشته‌ها هیچ‌وقت برای من مناسب نبود. رابط برنامه‌نویسی کاربردی OM تایپ‌شده CSS کمی طولانی است، اما امیدواریم که در آینده منجر به اشکالات کمتر و کد با عملکرد بهتر شود.