TL;DR
CSS hiện có API dựa trên đối tượng phù hợp để làm việc với các giá trị trong JavaScript.
el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'
Đã qua rồi thời kỳ để nối các chuỗi và các lỗi tinh vi!
Giới thiệu
CSSOM cũ
CSS đã có một mô hình đối tượng (CSSOM) được nhiều năm. Trên thực tế, bất cứ khi nào bạn đọc/đặt .style
trong JavaScript, bạn đều sử dụng nó:
// 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 mới đã nhập CSS
Mô hình đối tượng được nhập CSS (Typed OM) mới, nằm trong nỗ lực Houdini, mở rộng thế giới này bằng cách thêm các loại, phương thức và mô hình đối tượng phù hợp cho các giá trị CSS. Thay vì chuỗi, các giá trị được hiển thị dưới dạng đối tượng JavaScript để hỗ trợ thao tác CSS hiệu quả (và hợp lý).
Thay vì sử dụng element.style
, bạn sẽ truy cập các kiểu thông qua thuộc tính
.attributeStyleMap
mới cho các phần tử và thuộc tính .styleMap
cho
các quy tắc biểu định kiểu. Cả hai đều trả về một đối tượng 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');
Vì StylePropertyMap
là các đối tượng giống như bản đồ, nên StylePropertyMap
hỗ trợ tất cả các nghi ngờ thông thường (get/set/keys/values/Items), giúp mã này có thể xử lý linh hoạt:
// 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.
Lưu ý rằng trong ví dụ thứ hai, opacity
được đặt thành chuỗi ('0.3'
) nhưng một số sẽ xuất hiện khi thuộc tính được đọc lại sau.
Lợi ích
Vậy CSS đã nhập OM đang cố gắng giải quyết vấn đề gì? Xem các ví dụ ở trên (và trong suốt phần còn lại của bài viết này), bạn có thể lập luận rằng OM được nhập của CSS chi tiết hơn nhiều so với mô hình đối tượng cũ. Tôi đồng ý!
Trước khi bạn viết tắt OM được nhập, hãy cân nhắc một số tính năng chính mà OM này mang lại cho bảng:
Ít lỗi hơn, ví dụ: giá trị số luôn được trả về dưới dạng số, chứ không phải chuỗi.
el.style.opacity += 0.1; el.style.opacity === '0.30.1' // dragons!
Phép tính số học và chuyển đổi đơn vị: chuyển đổi giữa các đơn vị độ dài tuyệt đối (ví dụ:
px
->cm
) và thực hiện phép toán cơ bản.Kẹp và làm tròn giá trị. Đã nhập các giá trị vòng và/hoặc kẹp OM để chúng nằm trong phạm vi chấp nhận được cho một cơ sở lưu trú.
Hiệu suất tốt hơn. Trình duyệt phải làm ít việc hơn trong việc chuyển đổi tuần tự và giải tuần tự các giá trị chuỗi. Giờ đây, công cụ này áp dụng cách hiểu tương tự về các giá trị CSS trên JS và C++. Tab Akins đã chỉ ra một số điểm chuẩn hiệu suất sớm giúp OM Typed hoạt động nhanh hơn khoảng 30% về số thao tác/giây so với việc sử dụng CSSOM và chuỗi cũ. Điều này có thể có ý nghĩa đối với ảnh động CSS nhanh sử dụng
requestionAnimationFrame()
. crbug.com/808933 theo dõi thêm công việc hiệu suất trong Blink.Lỗi xử lý. Các phương thức phân tích cú pháp mới mang đến công cụ xử lý lỗi cho CSS.
"Tôi nên sử dụng tên hay chuỗi CSS được viết hoa kiểu lạc đà?" Bạn không cần phải đoán nếu tên được viết hoa kiểu lạc đà hoặc chuỗi (ví dụ:
el.style.backgroundColor
so vớiel.style['background-color']
). Tên thuộc tính CSS trong OM được nhập luôn là các chuỗi, khớp với những gì bạn thực sự viết trong CSS :)
Hỗ trợ trình duyệt và phát hiện tính năng
OM đã nhập đã được đưa vào Chrome 66 và đang được triển khai trong Firefox. Edge đã cho thấy các dấu hiệu hỗ trợ, nhưng vẫn chưa thêm tính năng này vào trang tổng quan về nền tảng của họ.
Để phát hiện tính năng, bạn có thể kiểm tra xem một trong các nhà máy dạng số CSS.*
có được xác định hay không:
if (window.CSS && CSS.number) {
// Supports CSS Typed OM.
}
Kiến thức cơ bản về API
Truy cập vào các kiểu
Các giá trị được tách biệt với các đơn vị trong OM được nhập của CSS. Việc lấy một kiểu sẽ trả về CSSUnitValue
chứa value
và 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
Kiểu đã tính toán
Các kiểu điện toán đã chuyển từ API trên window
sang phương thức mới trên HTMLElement
, computedStyleMap()
:
CSSOM cũ
el.style.opacity = 0.5;
window.getComputedStyle(el).opacity === "0.5" // Ugh, more strings!
OM đã nhập mới
el.attributeStyleMap.set('opacity', 0.5);
el.computedStyleMap().get('opacity').value // 0.5
Kẹp / làm tròn giá trị
Một trong những tính năng thú vị của mô hình đối tượng mới là tự động kẹp và/hoặc làm tròn các giá trị kiểu đã tính toán. Ví dụ: giả sử bạn cố gắng đặt opacity
thành một giá trị nằm ngoài phạm vi được chấp nhận, [0, 1]. Nhập OM sẽ kẹp giá trị thành 1
khi tính toán kiểu:
el.attributeStyleMap.set('opacity', 3);
el.attributeStyleMap.get('opacity').value === 3 // val not clamped.
el.computedStyleMap().get('opacity').value === 1 // computed style clamps value.
Tương tự, việc đặt z-index:15.4
làm tròn thành 15
để giá trị vẫn là một số nguyên.
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.
Giá trị số CSS
Các số được biểu thị bằng hai loại đối tượng CSSNumericValue
trong OM được nhập:
CSSUnitValue
– các giá trị chứa một loại đơn vị (ví dụ:"42px"
).CSSMathValue
– các giá trị chứa nhiều giá trị/đơn vị, chẳng hạn như biểu thức toán học (ví dụ:"calc(56em + 10%)"
).
Giá trị đơn vị
Các giá trị số đơn giản ("50%"
) được biểu thị bằng các đối tượng CSSUnitValue
.
Mặc dù bạn có thể trực tiếp tạo các đối tượng này (new CSSUnitValue(10, 'px')
), nhưng trong hầu hết trường hợp bạn sẽ sử dụng phương thức nhà máy 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'
Xem thông số của danh sách đầy đủ các phương thức CSS.*
.
Giá trị toán học
Đối tượng CSSMathValue
đại diện cho các biểu thức toán học và thường chứa nhiều giá trị/đơn vị. Ví dụ phổ biến là tạo biểu thức calc()
của CSS, nhưng có các phương thức cho tất cả các hàm 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)"
Biểu thức lồng nhau
Việc dùng các hàm toán học để tạo các giá trị phức tạp hơn sẽ có đôi chút khó hiểu. Dưới đây là một vài ví dụ để giúp bạn bắt đầu. Tôi đã thêm khoảng thụt đầu dòng bổ sung để dễ đọc hơn.
calc(1px - 2 * 3em)
sẽ có cấu trúc như sau:
new CSSMathSum(
CSS.px(1),
new CSSMathNegate(
new CSSMathProduct(2, CSS.em(3))
)
);
calc(1px + 2px + 3px)
sẽ có cấu trúc như sau:
new CSSMathSum(CSS.px(1), CSS.px(2), CSS.px(3));
calc(calc(1px + 2px) + 3px)
sẽ có cấu trúc như sau:
new CSSMathSum(
new CSSMathSum(CSS.px(1), CSS.px(2)),
CSS.px(3)
);
Toán tử số học
Một trong những tính năng hữu ích nhất của OM được nhập của CSS là bạn có thể thực hiện các phép toán trên đối tượng CSSUnitValue
.
Thao tác cơ bản
Các thao tác cơ bản (add
/sub
/mul
/div
/min
/max
) được hỗ trợ:
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))"
Lượt chuyển đổi
Bạn có thể chuyển đổi đơn vị độ dài tuyệt đối sang độ dài đơn vị khác:
// 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
Bình đẳng
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
Giá trị biến đổi CSS
Phép biến đổi CSS được tạo bằng CSSTransformValue
và truyền một loạt các giá trị biến đổi (ví dụ: CSSRotate
, CSScale
, CSSSkew
, CSSSkewX
, CSSSkewY
). Ví dụ: giả sử bạn muốn tạo lại CSS này:
transform: rotateZ(45deg) scale(0.5) translate3d(10px,10px,10px);
Đã dịch sang OM đã nhập:
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))
]);
Ngoài độ chi tiết (lolz!), CSSTransformValue
có một số tính năng thú vị. Lớp này có một thuộc tính boolean để phân biệt các phép biến đổi 2D và 3D, cũng như một phương thức .toMatrix()
để trả về giá trị biểu diễn DOMMatrix
của một phép biến đổi:
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
Ví dụ: tạo ảnh động cho một khối lập phương
Hãy xem một ví dụ thực tế về việc sử dụng phép biến đổi. Chúng ta sẽ sử dụng các biến đổi JavaScript và CSS để tạo ảnh động cho một hình khối.
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.
})();
Lưu ý là:
- Giá trị số có nghĩa là chúng ta có thể trực tiếp tăng góc bằng toán học!
- Thay vì chạm vào DOM hoặc đọc lại một giá trị trên mọi khung (ví dụ: không có
box.style.transform=`rotate(0,0,1,${newAngle}deg)`
), ảnh động sẽ được điều hướng bằng cách cập nhật đối tượng dữ liệuCSSTransformValue
cơ bản, cải thiện hiệu suất.
Bản minh hoạ
Bên dưới, bạn sẽ thấy một hình khối màu đỏ nếu trình duyệt của bạn hỗ trợ Typed OM. Khối lập phương sẽ bắt đầu xoay khi bạn di chuột lên nó. Ảnh động này sử dụng phần mềm CSS Typed OM! 🤘
Giá trị thuộc tính tuỳ chỉnh CSS
CSS var()
trở thành đối tượng CSSVariableReferenceValue
trong OM đã nhập.
Giá trị của chúng được phân tích cú pháp thành CSSUnparsedValue
vì chúng có thể lấy bất kỳ loại nào (px, %, em, rgba(), v.v.).
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'
Nếu muốn nhận giá trị của thuộc tính tuỳ chỉnh, bạn cần làm một số việc sau:
<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>
Giá trị vị trí
Các thuộc tính CSS có vị trí x/y được phân tách bằng dấu cách, chẳng hạn như object-position
, được biểu thị bằng các đối tượng 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
Các giá trị phân tích cú pháp
OM đã nhập giới thiệu các phương thức phân tích cú pháp cho nền tảng web! Điều này có nghĩa là cuối cùng bạn có thể phân tích cú pháp các giá trị CSS theo phương thức lập trình, trước khi cố gắng sử dụng! Chức năng mới này có thể là một giải pháp cứu hộ tiềm năng trong việc phát hiện các lỗi ban đầu và CSS không đúng định dạng.
Phân tích cú pháp một kiểu đầy đủ:
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)'
Phân tích cú pháp các giá trị thành CSSUnitValue
:
CSSNumericValue.parse('42.0px') // {value: 42, unit: 'px'}
// But it's easier to use the factory functions:
CSS.px(42.0) // '42px'
Xử lý lỗi
Ví dụ – hãy kiểm tra xem trình phân tích cú pháp CSS có hài lòng với giá trị transform
này hay không:
try {
const css = CSSStyleValue.parse('transform', 'translate4d(bogus value)');
// use css
} catch (err) {
console.err(err);
}
Kết luận
Cuối cùng, đã có mô hình đối tượng cập nhật cho CSS. Tôi cảm thấy việc xử lý chuỗi chưa phù hợp với mình. API OM được nhập của CSS hơi chi tiết, nhưng hy vọng rằng điều này sẽ dẫn đến ít lỗi hơn và mã có hiệu suất tốt hơn về sau.