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

Eric Bidelman

Özet

CSS artık JavaScript'teki değerlerle çalışmaya uygun nesne tabanlı bir 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 bir araya getirme ve incelikli hata ayıklama dönemi sona erdi.

Giriş

Eski CSSOM

CSS'nin uzun yıllardır bir nesne modeli (CSSOM) vardır. Aslında JavaScript'te .style kodunu okuduğunuzda/ayarladığınızda:

// 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 Yazılan OM

Houdini çalışmasının bir parçası olan yeni CSS Yazılı Nesne Modeli (OM Türü), CSS değerlerine türler, yöntemler ve uygun nesne modeli ekleyerek bu dünya görünümünü genişletir. Dizeler yerine değerler, CSS'nin performanslı (ve mantıklı) yönetimini kolaylaştırmak için JavaScript nesneleri olarak gösterilir.

element.style kullanmak yerine, stillere öğeler için yeni bir .attributeStyleMap özelliği ve stil sayfası kuralları için .styleMap özelliği aracılığıyla erişirsiniz. Her ikisi de bir 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 öğeleri Harita benzeri nesneler olduğundan, bilindik tüm şüpheli öğeleri (get/set/anahtarlar/değerler/girişler) destekler. Bu nedenle aşağıdakilerle çalışmak için esnektirler:

// 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 dizesinin ('0.3') olarak ayarlandığını ancak özellik daha sonra tekrar okunduğunda bir sayı ortaya çıktığını unutmayın.

Avantajları

Peki, CSS ile yazılan OM hangi sorunları çözmeye çalışıyor? Yukarıdaki örneklere (ve bu makalenin geri kalanında) bakarak, CSS Türü OM'nin eski nesne modelinden çok daha ayrıntılı olduğunu iddia edebilirsiniz. Katılıyorum.

Yazılı OM'yi yazmadan önce, tabloya getirdiği temel özelliklerden bazılarını göz önünde bulundurun:

  • Daha az hata: Örneğin, sayısal değerler her zaman dize 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) ve temel matematik arasında dönüştürme yapın.

  • Değer sınırlama ve yuvarlama. Yazılan OM yuvarları ve/veya sabitler değerleri, bir mülkün kabul edilebilir aralıkları içinde olur.

  • Daha iyi performans. Tarayıcının dize değerlerini serileştirme ve serileştirmeden daha az işi gerekir. Artık motor, JS ve C++ genelinde CSS değerlerini benzer bir anlayışla kullanmaktadır. Tab Akins, eski CSSOM ve dizelere kıyasla, yazılan OM'nin işlem/saniyede yaklaşık% 30 daha hızlı olmasını sağlayan bazı erken performans karşılaştırmaları göstermektedir. Bu, requestionAnimationFrame() kullanan 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şleme olanağını sunuyor.

  • "Büyük-küçük harf içeren CSS adları veya dizeleri kullanmalı mıyım?" Adların büyük/küçük harf mi yoksa dize mi (ör. el.style.backgroundColor veya el.style['background-color']) olduğunu tahmin etmenize gerek yok. Yazılan OM'deki CSS özellik adları her zaman, CSS'de yazdıklarınızla eşleşen dizelerdir :)

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

Yazılan OM, Chrome 66'da açılır ve Firefox'ta uygulanmaktadır. Edge, destek işaretleri göstermiş olsa da bunu platform kontrol paneline henüz eklememiştir.

Ö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

Değerler, CSS Yazılı OM'deki birimlerden ayrıdır. Bir stil alınması, 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

Hesaplanan stiller

Hesaplanan stiller window tarihindeki bir API'den HTMLElement itibarıyla 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ılan OM

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

Değer sınırlama / yuvarlama

Yeni nesne modelinin güzel özelliklerinden biri, hesaplanan stil değerlerinin otomatik olarak sınırlanması 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 değerine 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 değerinin ayarlanması, değerin tam sayı olarak kalması için 15 değerine yuvarlanı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, Yazılı OM'de iki tür CSSNumericValue nesnesiyle temsil edilir:

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

Birim değerleri

Basit sayısal değerler ("50%") CSSUnitValue nesneleriyle temsil edilir. Bu nesneleri doğrudan oluşturabilirsiniz ancak (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 spesifikasyona bakın.

Matematik değerleri

CSSMathValue nesneleri matematiksel ifadeleri temsil eder ve genellikle birden fazla değer/birim içerir. Yaygın bir örnek, CSS calc() ifadesi oluşturmaktır, ancak tüm CSS işlevleri için kullanılabilen yöntemler vardır: 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)"

İç içe yerleştirilmiş ifadeler

Daha karmaşık değerler oluşturmak için matematik fonksiyonlarını kullanmak biraz kafa karıştırıcıdır. Aşağıda, başlamanıza yardımcı olacak birkaç örnek verilmiştir. Daha kolay okunması için fazladan 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 Türü OM'nin en kullanışlı özelliklerinden biri, CSSUnitValue nesneleri üzerinde matematik işlemleri gerçekleştirebilmenizdir.

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 birim uzunluklarına 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üşüm değerleri

CSS dönüşümleri CSSTransformValue ile oluşturulur ve bir dönüşüm değerleri dizisi iletir (ör. CSSRotate, CSScale, CSSSkew, CSSSkewX, CSSSkewY). Örnek olarak, şu CSS'yi yeniden oluşturmak istediğinizi varsayalım:

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

Yazılan OM'ye çevrildi:

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

Ayrıntı düzeyinin yanı sıra (hahaha!), CSSTransformValue birtakım etkileyici özelliklere sahip. 2D ve 3D dönüşümleri ayırt etmek için bir boole özelliği ve bir dönüşümün DOMMatrix gösterimini döndürmek için .toMatrix() yöntemi bulunur:

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üp animasyon

Dönüşümleri kullanmaya ilişkin pratik bir örneği inceleyelim. Bir küpü canlandırmak için JavaScript ve CSS dönüşümlerini 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.
})();

Şunlara dikkat edin:

  1. Sayısal değerler, açıyı doğrudan matematik kullanarak artırabileceğimiz anlamına gelir.
  2. Animasyon, DOM'a dokunmak veya her karede bir değeri (ör. box.style.transform=`rotate(0,0,1,${newAngle}deg)` yok) okumak yerine temel CSSTransformValue veri nesnesinin güncellenip performansın artırılmasıyla yönetilir.

Demo

Tarayıcınız Yazılı OM'yi destekliyorsa aşağıda kırmızı bir küp görürsünüz. Fareyi üzerine getirdiğinizde küp dönmeye başlar. Animasyon, CSS Yazarak OM! 🤘

CSS özel özellik değerleri

CSS var(), Yazılan OM'de CSSVariableReferenceValue nesnesi haline geldi. Herhangi bir türü (px, %, em, rgba() vb.) alabildiği için değerleri 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'

Bir özel özelliğin değerini almak istiyorsanız yapmanız gereken biraz iş 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ış x/y konumu alan CSS özellikleri CSSPositionValue nesneleriyle temsil edilir.

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ğerler ayrıştırılıyor

Typed OM, web platformuna ayrıştırma yöntemleri sunuyor. Yani son olarak CSS değerlerini, kullanmayı denemeden önce programatik olarak ayrıştırabilirsiniz. Bu yeni özellik, erken hataları ve bozuk CSS'yi yakalamak için potansiyel bir hayat kurtarır.

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

Nihayet CSS için güncellenmiş bir nesne modeli oluşturmak güzel. Tellerle çalışmak bana hiç doğru gelmedi. CSS Typed OM API'si biraz ayrıntılı olsa da daha az hata ve daha yüksek performanslı kod sağlar.