کار با 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)، بخشی از تلاش هودینی ، این جهان بینی را با افزودن انواع، روش ها و یک مدل شی مناسب به مقادیر CSS گسترش می دهد. به جای رشته ها، مقادیر به عنوان اشیاء جاوا اسکریپت در معرض دید قرار می گیرند تا دستکاری عملکردی (و معقول) 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/value/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 را تایپ کرده و/یا مقادیر را گیره می دهد تا در محدوده های قابل قبول برای یک ویژگی باشند.

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

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

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

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

OM تایپ شده در کروم 66 فرود آمد و در فایرفاکس در حال پیاده سازی است. 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

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

سبک های محاسبه شده از یک 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)
);

عملیات حسابی

یکی از مفیدترین ویژگی های 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 علاوه بر پرحرفی (lolz!)، ویژگی های جالبی دارد. دارای یک خاصیت بولی برای متمایز کردن تبدیل‌های دو بعدی و سه بعدی و یک متد .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

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

بیایید یک مثال کاربردی از استفاده از تبدیل ها را ببینیم. ما از جاوا اسکریپت و تبدیل های 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 یا بازخوانی یک مقدار در هر فریم (مثلاً بدون 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 کمی پرمخاطب است، اما امیدواریم که منجر به باگ های کمتر و کدهای کارآمدتر شود.