Yeni CSS Yazılı Nesne Modeli ile çalışma

Özet

CSS artık JavaScript'teki değerlerle çalışmak için uygun bir nesne tabanlı API'ye sahip.

el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'

Dizeleri birleştirme ve küçük hatalarla uğraşma dönemi sona erdi.

Giriş

Eski CSSOM

CSS, uzun yıllardır bir nesne modeline (CSSOM) sahiptir. Hatta JavaScript'te .style okuduğunuz veya ayarladığınız her seferde bu özelliği kullanırsınız:

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

Yeni CSS Typed OM

Houdini projesinin bir parçası olan yeni CSS Typed Object Model (Typed OM), CSS değerlerine türler, yöntemler ve uygun bir nesne modeli ekleyerek bu dünya görüşünü genişletir. Değerler, dizeler yerine CSS'nin performanslı (ve mantıklı) şekilde işlenmesini kolaylaştırmak için JavaScript nesneleri olarak sunulur.

element.style yerine, öğeler için yeni bir .attributeStyleMap özelliği ve stil sayfası kuralları için bir .styleMap özelliği aracılığıyla stillere erişeceksiniz. Her ikisi de StylePropertyMap nesnesi döndürür.

// 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 benzeri nesneler olduğundan, tüm olağan şüphelileri (get/set/keys/values/entries) destekler ve bu da onları esnek bir şekilde kullanmanızı sağlar:

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

İkinci örnekte opacity değerinin dize ('0.3') olarak ayarlandığını ancak özellik daha sonra tekrar okunduğunda sayının geri döndüğünü unutmayın.

Avantajları

Peki CSS Typed OM hangi sorunları çözmeye çalışıyor? Yukarıdaki örnekleri (ve bu makalenin geri kalanını) incelediğinizde, CSS Typed OM'nin eski nesne modelinden çok daha ayrıntılı olduğunu düşünebilirsiniz. Katılıyorum.

Typed OM'yi kullanmayı bırakmadan önce sunduğu temel özelliklerden bazılarını göz önünde bulundurun:

  • Daha az hata: Örneğin, sayısal değerler her zaman dize olarak değil, sayı olarak döndürülür.

    el.style.opacity += 0.1;
    el.style.opacity === '0.30.1' // dragons!
    
  • Aritmetik işlemler ve birim dönüştürme: Mutlak uzunluk birimleri (ör. px -> cm) arasında dönüştürme yapın ve temel matematik işlemlerini gerçekleştirin.

  • Değer sınırlama ve yuvarlama. Yazılan OM, değerleri bir özellik için kabul edilebilir aralıklar içinde olacak şekilde yuvarlar ve/veya sınırlar.

  • Daha iyi performans. Tarayıcının, dize değerlerini serileştirme ve seri durumdan çıkarma konusunda daha az iş yapması gerekir. Artık motor, JS ve C++'da CSS değerlerini benzer şekilde anlıyor. Tab Akins, erken performans karşılaştırmaları gösterdi. Bu karşılaştırmalara göre, eski CSSOM ve dizeleri kullanmaya kıyasla Typed OM, işlemler/sn cinsinden % 30 daha hızlı. Bu, requestionAnimationFrame() kullanılarak yapılan hızlı CSS animasyonları için önemli olabilir. crbug.com/808933, Blink'teki ek performans çalışmalarını izler.

  • Hata işleme. Yeni ayrıştırma yöntemleri, CSS dünyasında hata işlemeyi mümkün kılıyor.

  • "Deve harfiyle yazılmış CSS adları mı yoksa dizeler mi kullanmalıyım?" Adların camel case mi yoksa dize mi olduğu (ör.el.style.backgroundColor ve el.style['background-color']) konusunda artık tahminde bulunmanız gerekmiyor. Typed OM'deki CSS özelliği adları her zaman dizelerdir ve CSS'de yazdıklarınızla eşleşir. :)

Tarayıcı desteği ve özellik algılama

Yazılan OM, Chrome 66'da kullanıma sunuldu ve Firefox'ta uygulanıyor. Edge, destek sinyalleri gösterse de henüz platform kontrol paneline eklemedi.

Özellik algılama için CSS.* sayısal fabrikalardan birinin tanımlanıp tanımlanmadığını kontrol edebilirsiniz:

if (window.CSS && CSS.number) {
  // Supports CSS Typed OM.
}

API Temel Bilgileri

Stillere erişme

CSS Typed OM'de değerler birimlerden ayrıdır. Stil alma işlemi, value ve unit içeren bir CSSUnitValue döndürür:

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

Hesaplanmış stiller

Hesaplanmış stiller window üzerindeki bir API'den HTMLElement üzerindeki yeni bir yönteme taşındı. computedStyleMap():

Eski CSSOM

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

Yeni Yazılı OM

el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5

Değer kısıtlama / yuvarlama

Yeni nesne modelinin güzel özelliklerinden biri de hesaplanmış stil değerlerinin otomatik olarak sınırlandırılması ve/veya yuvarlanmasıdır. Örneğin, opacity değerini kabul edilebilir aralığın [0, 1] dışında bir değere ayarlamaya çalıştığınızı varsayalım. Yazılan OM, stili hesaplarken değeri 1 olarak sabitler:

el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3  // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.

Benzer şekilde, z-index:15.4 ayarı 15 olarak yuvarlanır ve değer tam sayı olarak kalır.

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 sayısal değerleri

Sayılar, Typed OM'de iki tür CSSNumericValue nesneyle temsil edilir:

  1. CSSUnitValue - Tek bir birim türü içeren değerler (ör. "42px").
  2. CSSMathValue - Matematiksel ifade (ör. "calc(56em + 10%)") gibi birden fazla değer/birim içeren değerler.

Birim değerleri

Basit sayısal değerler ("50%"), CSSUnitValue nesneleriyle temsil edilir. Bu nesneleri doğrudan oluşturmanız mümkün olsa da (new CSSUnitValue(10, 'px')) , çoğu zaman CSS.* fabrika yöntemlerini kullanırsınız:

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.* yöntemlerinin tam listesi için teknik özelliklere bakın.

Matematik değerleri

CSSMathValue nesneleri matematiksel ifadeleri temsil eder ve genellikle birden fazla değer/birim içerir. Yaygın örnek, bir CSS calc() ifadesi oluşturmaktır ancak tüm CSS işlevleri (calc(), min(), max()) için yöntemler vardır.

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)"

İç içe yerleştirilmiş ifadeler

Daha karmaşık değerler oluşturmak için matematik işlevlerini kullanmak biraz kafa karıştırıcı olabilir. Başlamanıza yardımcı olacak birkaç örneği aşağıda bulabilirsiniz. Daha kolay okunabilmeleri için bu paragraflara ek girinti ekledim.

calc(1px - 2 * 3em) şu şekilde oluşturulur:

new CSSMathSum(
  CSS.px(1),
  new CSSMathNegate(
    new CSSMathProduct(2, CSS.em(3))
  )
);

calc(1px + 2px + 3px) şu şekilde oluşturulur:

new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));

calc(calc(1px + 2px) + 3px) şu şekilde oluşturulur:

new CSSMathSum(
  new CSSMathSum(CSS.px(1), CSS.px(2)),
  CSS.px(3)
);

Aritmetik işlemler

CSS Typed OM'nin en yararlı özelliklerinden biri, CSSUnitValue nesneleri üzerinde matematiksel işlemler yapabilmenizdir.

Temel işlemler

Temel işlemler (add/sub/mul/div/min/max) desteklenir:

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))"

Dönüşüm

Mutlak uzunluk birimleri diğer uzunluk birimlerine dönüştürülebilir:

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

Eşitlik

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 dönüştürme değerleri

CSS dönüştürmeleri CSSTransformValue ile oluşturulur ve dönüştürme değerleri dizisi (ör. CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY) iletilir. Örneğin, şu CSS'yi yeniden oluşturmak istediğinizi varsayalım:

transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);

Yazılı OM'ye çevrilenler:

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

Çok uzun olmasının (lolz!) yanı sıra, CSSTransformValue'da bazı harika özellikler var. 2D ve 3D dönüşümleri ayırt etmek için bir Boole özelliği ve dönüşümün DOMMatrix gösterimini döndürmek için bir .toMatrix() yöntemi vardır:

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

Örnek: küpe animasyon ekleme

Dönüşümleri kullanmayla ilgili pratik bir örneğe göz atalım. Bir küpe animasyon eklemek için JavaScript ve CSS dönüştürmelerini kullanacağız.

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.
})();

Unutmayın:

  1. Sayısal değerler, açıyı doğrudan matematik kullanarak artırabileceğimiz anlamına gelir.
  2. Her karede DOM'a dokunmak veya bir değeri geri okumak yerine (ör. box.style.transform=`rotate(0,0,1,${newAngle}deg)` yok), animasyon temel CSSTransformValue veri nesnesi güncellenerek yönlendirilir ve performans iyileştirilir.

Demo

Tarayıcınız Typed OM'yi destekliyorsa aşağıda kırmızı bir küp görürsünüz. Fare imlecini küpün üzerine getirdiğinizde küp dönmeye başlar. Animasyon, CSS Typed OM tarafından desteklenir. 🤘

CSS özel özellik değerleri

CSS var(), Typed OM'de CSSVariableReferenceValue nesnesi haline gelir. Değerleri herhangi bir türü (px, %, em, rgba() vb.) alabileceğinden CSSUnparsedValue olarak ayrıştırılır.

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'

Özel bir özelliğin değerini almak istiyorsanız yapmanız gereken birkaç işlem vardır:

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

Konum değerleri

object-position gibi boşlukla ayrılmış bir x/y konumu alan CSS özellikleri CSSPositionValue nesneleriyle gösterilir.

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

Değerleri ayrıştırma

Typed OM, web platformuna ayrıştırma yöntemleri sunuyor. Bu sayede, CSS değerlerini programatik olarak ayrıştırabilirve kullanmaya çalışmadanönce doğrulayabilirsiniz. Bu yeni özellik, hataları ve hatalı CSS'leri erken aşamada yakalamak için hayat kurtarıcı olabilir.

Tam stili ayrıştırma:

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)'

Değerleri CSSUnitValue içine ayrıştırın:

CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}

// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'

Hata işleme

Örnek: CSS ayrıştırıcının bu transform değerinden memnun olup olmayacağını kontrol edin:

try {
  const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
  // use css
} catch (err) {
  console.err(err);
}

Sonuç

CSS için nihayet güncellenmiş bir nesne modeline sahip olmak güzel. Dizelerle çalışmak hiç bana göre olmadı. CSS Typed OM API biraz ayrıntılı olsa da ileride daha az hata ve daha yüksek performanslı kodla sonuçlanacağını umuyoruz.