TL;DR (Ringkasan)
CSS kini memiliki API berbasis objek yang tepat untuk menggunakan nilai di JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
Masa-masa menggabungkan string dan bug halus sudah berakhir!
Pengantar
Engage Lama
CSS telah memiliki model objek (CSSOM) selama bertahun-tahun. Bahkan, setiap
kali Anda membaca/menetapkan .style
di JavaScript, Anda sedang menggunakannya:
// 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;
OM dengan Jenis CSS Baru
CSS Typed Object Model (Typed OM) baru yang merupakan bagian dari upaya Houdini, memperluas pandangan umum ini dengan menambahkan jenis, metode, dan model objek yang tepat ke nilai CSS. Sebagai ganti string, nilai ditampilkan sebagai objek JavaScript untuk memfasilitasi manipulasi CSS yang berperforma tinggi (dan logis).
Sebagai ganti menggunakan element.style
, Anda akan mengakses gaya melalui properti .attributeStyleMap
baru untuk elemen dan properti .styleMap
untuk aturan stylesheet. Keduanya menampilkan objek 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');
Karena StylePropertyMap
adalah objek mirip Peta, objek ini mendukung semua kecurigaan
biasa (get/set/keys/values/entries), sehingga fleksibel untuk digunakan:
// 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.
Perhatikan bahwa dalam contoh kedua, opacity
disetel ke string ('0.3'
), tetapi
angka akan kembali saat properti dibaca kembali nanti.
Manfaat
Jadi masalah apa yang coba dipecahkan oleh CSS Typed OM? Dengan melihat contoh di atas (dan di sepanjang artikel ini), Anda mungkin berpendapat bahwa CSS Typed OM jauh lebih panjang daripada model objek lama. Saya setuju.
Sebelum menghapus Typed OM, pertimbangkan beberapa fitur utama yang dibawanya ke tabel:
Lebih sedikit bug. misalnya, nilai numerik selalu ditampilkan sebagai angka, bukan string.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
Operasi aritmetika & konversi satuan. mengonversi antara satuan panjang absolut (mis.
px
->cm
) dan melakukan penghitungan dasar.Pembulatan & pembulatan nilai. Nilai bulat dan/atau klem OM yang diketik sehingga berada dalam rentang yang dapat diterima untuk sebuah properti.
Performa yang lebih baik. Browser harus melakukan lebih sedikit pekerjaan serialisasi dan deserialisasi nilai string. Sekarang, mesin ini menggunakan pemahaman serupa tentang nilai CSS di seluruh JS dan C++. Tab Akins telah menunjukkan beberapa tolok ukur performa awal yang menempatkan Typed OM pada ~30% lebih cepat dalam operasi/dtk jika dibandingkan dengan menggunakan WorkManager dan string lama. Hal ini bisa menjadi signifikan untuk animasi CSS cepat yang menggunakan
requestionAnimationFrame()
. crbug.com/808933 melacak pekerjaan performa tambahan di Blink.Penanganan error. Metode penguraian baru menghadirkan penanganan error di dunia CSS.
"Haruskah saya menggunakan nama atau string CSS ber-cased?" Tidak perlu lagi menebak apakah nama menggunakan camel-case atau string (misalnya
el.style.backgroundColor
vsel.style['background-color']
). Nama properti CSS di Typed OM selalu berupa string, sesuai dengan yang sebenarnya Anda tulis di CSS :)
Dukungan browser & deteksi fitur
Ketik OM muncul di Chrome 66 dan sedang diterapkan di Firefox. Edge telah menunjukkan tanda-tanda dukungan, tetapi belum menambahkannya ke dasbor platform mereka.
Untuk deteksi fitur, Anda dapat memeriksa apakah salah satu factory numerik CSS.*
ditentukan:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Dasar-Dasar API
Mengakses gaya
Nilai terpisah dari unit di OM Berjenis CSS. Mendapatkan gaya akan menampilkan CSSUnitValue
yang berisi value
dan 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
Gaya yang dihitung
Gaya terkomputasi telah dipindahkan dari API pada window
ke metode baru pada HTMLElement
, computedStyleMap()
:
Googlebot Lama
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
OM dengan Jenis Baru
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Pembulatan / penjepit nilai
Salah satu fitur menarik dari model objek baru adalah penjepitan dan/atau
pembulatan nilai gaya terkomputasi secara otomatis. Sebagai contoh, misalkan Anda mencoba menetapkan opacity
ke nilai di luar rentang yang dapat diterima, [0, 1]. OM yang diketik akan membatasi
nilai menjadi 1
saat menghitung gaya:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
Demikian pula, menyetel z-index:15.4
akan dibulatkan ke 15
sehingga nilainya tetap
bilangan bulat.
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.
Nilai numerik CSS
Angka direpresentasikan oleh dua jenis objek CSSNumericValue
di Typed OM:
CSSUnitValue
- nilai yang berisi satu jenis unit (misalnya"42px"
).CSSMathValue
- nilai yang berisi lebih dari satu nilai/unit, seperti ekspresi matematika (mis."calc(56em + 10%)"
).
Nilai satuan
Nilai numerik sederhana ("50%"
) diwakili oleh objek CSSUnitValue
.
Meskipun Anda dapat membuat objek ini secara langsung (new CSSUnitValue(10, 'px')
), biasanya Anda akan menggunakan metode factory 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'
Lihat spesifikasi untuk daftar lengkap
metode CSS.*
.
Nilai matematika
Objek CSSMathValue
mewakili ekspresi matematika dan biasanya
berisi lebih dari satu nilai/unit. Contoh umumnya adalah membuat ekspresi calc()
CSS, tetapi ada beberapa metode untuk semua fungsi 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)"
Ekspresi bertingkat
Menggunakan fungsi matematika untuk membuat nilai yang lebih kompleks akan agak membingungkan. Berikut ini beberapa contoh untuk membantu Anda memulai. saya telah menambahkan indentasi ekstra untuk membuatnya lebih mudah dibaca.
calc(1px - 2 * 3em)
akan dibuat sebagai:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
akan dibuat sebagai:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
akan dibuat sebagai:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Operasi aritmetika
Salah satu fitur OM Berjenis CSS yang paling berguna adalah Anda dapat menjalankan operasi matematika pada objek CSSUnitValue
.
Operasi dasar
Operasi dasar (add
/sub
/mul
/div
/min
/max
) didukung:
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))"
Konversi
Satuan panjang absolut dapat dikonversi ke panjang satuan lainnya:
// 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
Persamaan
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
Nilai transformasi CSS
Transformasi CSS dibuat dengan CSSTransformValue
dan meneruskan array nilai transformasi (misalnya CSSRotate
, CSScale
, CSSSkew
, CSSSkewX
, CSSSkewY
). Sebagai contoh, Anda ingin membuat ulang CSS ini:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Diterjemahkan ke dalam Typed 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))
]);
Selain panjangnya (lolz!), CSSTransformValue
memiliki beberapa
fitur keren. Class ini memiliki properti boolean untuk membedakan transformasi 2D dan 3D
serta metode .toMatrix()
untuk menampilkan representasi DOMMatrix
transformasi:
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
Contoh: menganimasikan kubus
Mari kita lihat contoh praktis penggunaan transformasi. Kita akan menggunakan transformasi JavaScript dan CSS untuk menganimasikan kubus.
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.
})();
Perhatikan bahwa:
- Nilai numerik berarti kita dapat menambah sudut secara langsung menggunakan matematika.
- Alih-alih menyentuh DOM atau membaca kembali nilai pada setiap frame (misalnya, tanpa
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
), animasi didorong dengan memperbarui objek dataCSSTransformValue
yang mendasarinya, sehingga meningkatkan performa.
Demo
Di bawah ini, Anda akan melihat kubus merah jika browser Anda mendukung Typed OM. Kubus mulai berputar saat Anda mengarahkan kursor ke atasnya. Animasi ini didukung oleh CSS Typed OM! 🤘
Nilai properti kustom CSS
var()
CSS menjadi objek CSSVariableReferenceValue
di OM yang Diketik.
Nilainya diuraikan menjadi CSSUnparsedValue
karena dapat mengambil jenis apa pun (px, %, em, rgba(), dll.).
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'
Jika Anda ingin mendapatkan nilai properti khusus, ada sedikit pekerjaan yang harus dilakukan:
<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>
Nilai posisi
Properti CSS yang mengambil posisi x/y yang dipisahkan spasi seperti
object-position
direpresentasikan oleh objek 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
Mengurai nilai
Typed OM memperkenalkan metode penguraian ke platform web. Artinya, Anda akhirnya dapat mengurai nilai CSS secara terprogram, sebelum mencoba menggunakannya. Kemampuan baru ini berpotensi menyelamatkan nyawa untuk menangkap bug awal dan format CSS yang salah.
Uraikan gaya lengkap:
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)'
Uraikan nilai ke dalam CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Penanganan error
Contoh - periksa apakah parser CSS akan puas dengan nilai transform
ini:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Kesimpulan
Senang rasanya bisa memiliki model objek yang diperbarui untuk CSS. Bekerja dengan {i>string<i} tidak terasa tepat bagi saya. CSS Typed OM API sedikit panjang, tetapi semoga menghasilkan lebih sedikit bug dan kode yang lebih berperforma tinggi nantinya.