CSS và kiểu
Bài viết này thảo luận thêm về những điều tuyệt vời mà bạn có thể làm với Shadow DOM. Bài viết này dựa trên các khái niệm được thảo luận trong phần Shadow DOM 101. Nếu bạn đang tìm hiểu về cách giới thiệu, hãy xem bài viết đó.
Giới thiệu
Hãy đối mặt với thực tế. Mã đánh dấu không theo kiểu không có gì hấp dẫn. May mắn thay, những người tài giỏi đứng sau Web Components đã lường trước được điều này và không để chúng tôi phải chờ đợi. Mô-đun CSS Scoping (Mô-đun CSS xác định phạm vi) xác định nhiều tuỳ chọn để tạo kiểu cho nội dung trong cây bóng.
Đóng gói kiểu
Một trong những tính năng cốt lõi của Shadow DOM là ranh giới bóng. Kiểu này có nhiều thuộc tính thú vị, nhưng một trong những thuộc tính tốt nhất là cung cấp tính năng đóng gói kiểu miễn phí. Nói cách khác:
<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
`;
</script>
Có hai điều thú vị về bản minh hoạ này:
- Có các h3 khác trên trang này, nhưng chỉ có một h3 khớp với bộ chọn h3 và do đó được tạo kiểu màu đỏ là h3 trong ShadowRoot. Xin nhắc lại, các kiểu theo phạm vi theo mặc định.
- Các quy tắc kiểu khác được xác định trên trang này nhắm đến h3 không ảnh hưởng đến nội dung của tôi. Đó là do các bộ chọn không vượt qua ranh giới bóng.
Ý nghĩa của câu chuyện? Chúng ta có đóng gói kiểu từ bên ngoài. Cảm ơn Shadow DOM!
Định kiểu cho phần tử lưu trữ
:host
cho phép bạn chọn và tạo kiểu cho phần tử lưu trữ cây bóng:
<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
<style>
:host {
text-transform: uppercase;
}
</style>
<content></content>
`;
</script>
Một điểm cần lưu ý là các quy tắc trong trang mẹ có mức độ cụ thể cao hơn so với các quy tắc :host
được xác định trong phần tử, nhưng có mức độ cụ thể thấp hơn so với thuộc tính style
được xác định trên phần tử lưu trữ. Điều này cho phép người dùng ghi đè kiểu của bạn từ bên ngoài.
:host
cũng chỉ hoạt động trong ngữ cảnh của ShadowRoot, vì vậy, bạn không thể sử dụng nó bên ngoài Shadow DOM.
Biểu thức hàm của :host(<selector>)
cho phép bạn nhắm đến phần tử lưu trữ nếu phần tử đó khớp với <selector>
.
Ví dụ – chỉ so khớp nếu chính phần tử đó có lớp .different
(ví dụ: <x-foo class="different"></x-foo>
):
:host(.different) {
...
}
Phản ứng với trạng thái người dùng
Một trường hợp sử dụng phổ biến của :host
là khi bạn tạo một Phần tử tuỳ chỉnh và muốn phản ứng với các trạng thái người dùng khác nhau (:hover, :focus, :active, v.v.).
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
Tuỳ chỉnh giao diện cho một phần tử
Lớp giả lập :host-context(<selector>)
khớp với phần tử lưu trữ nếu phần tử đó hoặc bất kỳ phần tử cấp trên nào khớp với <selector>
.
Một cách sử dụng phổ biến của :host-context()
là để tạo giao diện cho một phần tử dựa trên môi trường xung quanh. Ví dụ: nhiều người tạo giao diện bằng cách áp dụng một lớp cho <html>
hoặc <body>
:
<body class="different">
<x-foo></x-foo>
</body>
Bạn có thể :host-context(.different)
để tạo kiểu cho <x-foo>
khi phần tử này là phần tử con của một phần tử có lớp .different
:
:host-context(.different) {
color: red;
}
Điều này cho phép bạn đóng gói các quy tắc kiểu trong Shadow DOM của một phần tử để tạo kiểu riêng cho phần tử đó, dựa trên ngữ cảnh của phần tử.
Hỗ trợ nhiều loại máy chủ lưu trữ trong một gốc bóng
Một cách sử dụng khác của :host
là nếu bạn đang tạo một thư viện giao diện và muốn hỗ trợ tạo kiểu cho nhiều loại phần tử lưu trữ trong cùng một Shadow DOM.
:host(x-foo) {
/* Applies if the host is a <x-foo> element.*/
}
:host(x-foo:host) {
/* Same as above. Applies if the host is a <x-foo> element. */
}
:host(div) {
/* Applies if the host element is a <div>. */
}
Định kiểu nội bộ Shadow DOM từ bên ngoài
Phần tử giả ::shadow
và bộ kết hợp /deep/
giống như một thanh kiếm Vorpal có thẩm quyền CSS.
Các lớp này cho phép xuyên qua ranh giới của Shadow DOM để tạo kiểu cho các phần tử trong cây bóng đổ.
Phần tử giả ::shadow
Nếu một phần tử có ít nhất một cây bóng, thì phần tử giả ::shadow
sẽ khớp với chính gốc bóng.
Phương thức này cho phép bạn viết bộ chọn định kiểu các nút nội bộ trong shadow dom của một phần tử.
Ví dụ: nếu một phần tử đang lưu trữ một gốc bóng, bạn có thể viết #host::shadow span {}
để tạo kiểu cho tất cả các span trong cây bóng của phần tử đó.
<style>
#host::shadow span {
color: red;
}
</style>
<div id="host">
<span>Light DOM</span>
</div>
<script>
var host = document.querySelector('div');
var root = host.createShadowRoot();
root.innerHTML = `
<span>Shadow DOM</span>
<content></content>
`;
</script>
Ví dụ (phần tử tuỳ chỉnh) – <x-tabs>
có <x-panel>
con trong Shadow DOM. Mỗi bảng điều khiển lưu trữ cây bóng riêng chứa các tiêu đề h2
. Để tạo kiểu cho các tiêu đề đó từ trang chính, bạn có thể viết:
x-tabs::shadow x-panel::shadow h2 {
...
}
Toán tử kết hợp /deep/
Toán tử kết hợp /deep/
tương tự như ::shadow
, nhưng mạnh mẽ hơn. Phương thức này hoàn toàn bỏ qua tất cả ranh giới bóng và đi qua bất kỳ số lượng cây bóng nào. Nói một cách đơn giản, /deep/
cho phép bạn đi sâu vào nội dung của một phần tử và nhắm đến bất kỳ nút nào.
Toán tử kết hợp /deep/
đặc biệt hữu ích trong thế giới của Phần tử tuỳ chỉnh, nơi thường có nhiều cấp độ DOM tối. Ví dụ điển hình là lồng một nhóm các phần tử tuỳ chỉnh (mỗi phần tử lưu trữ cây bóng riêng) hoặc tạo một phần tử kế thừa từ một phần tử khác bằng cách sử dụng <shadow>
.
Ví dụ (phần tử tuỳ chỉnh) – chọn tất cả phần tử <x-panel>
là phần tử con của <x-tabs>
, ở bất kỳ vị trí nào trong cây:
x-tabs /deep/ x-panel {
...
}
Ví dụ – tạo kiểu cho tất cả các phần tử bằng lớp .library-theme
, ở bất kỳ vị trí nào trong cây bóng:
body /deep/ .library-theme {
...
}
Làm việc với querySelector()
Giống như .shadowRoot
mở
cây bóng khi di chuyển qua DOM, các toán tử kết hợp sẽ mở cây bóng khi di chuyển qua bộ chọn.
Thay vì viết một chuỗi lồng nhau điên rồ, bạn có thể viết một câu lệnh duy nhất:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
Định kiểu cho các phần tử gốc
Các thành phần điều khiển HTML gốc là một thách thức đối với kiểu. Nhiều người chỉ cần bỏ cuộc và tự tạo. Tuy nhiên, với ::shadow
và /deep/
, bạn có thể tạo kiểu cho bất kỳ phần tử nào trong nền tảng web sử dụng Shadow DOM. Ví dụ điển hình là các loại <input>
và <video>
:
video /deep/ input[type="range"] {
background: hotpink;
}
Tạo phần lồng ghép kiểu
Có thể tuỳ chỉnh. Trong một số trường hợp, bạn có thể muốn tạo lỗ trong khi tạo kiểu cho Shadow và tạo các móc để người khác tạo kiểu.
Sử dụng ::shadow và /deep/
/deep/
có rất nhiều sức mạnh. Phương thức này giúp tác giả thành phần chỉ định các phần tử riêng lẻ là có thể tạo kiểu hoặc một loạt các phần tử là có thể tạo giao diện.
Ví dụ – tạo kiểu cho tất cả các phần tử có lớp .library-theme
, bỏ qua tất cả cây bóng:
body /deep/ .library-theme {
...
}
Sử dụng phần tử mô phỏng tuỳ chỉnh
Cả WebKit và Firefox đều xác định các phần tử giả để tạo kiểu cho các phần nội bộ của phần tử trình duyệt gốc. Một ví dụ điển hình là input[type=range]
. Bạn có thể tạo kiểu cho nút trượt <span style="color:blue">blue</span>
bằng cách nhắm đến ::-webkit-slider-thumb
:
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
Tương tự như cách trình duyệt cung cấp các móc định kiểu vào một số thành phần nội bộ, tác giả của nội dung Shadow DOM có thể chỉ định một số phần tử nhất định là có thể định kiểu bởi các thành phần bên ngoài. Bạn có thể thực hiện việc này thông qua các phần tử giả tuỳ chỉnh.
Bạn có thể chỉ định một phần tử làm phần tử giả tuỳ chỉnh bằng cách sử dụng thuộc tính pseudo
.
Giá trị hoặc tên của lớp này cần có tiền tố "x-". Việc này sẽ tạo ra một mối liên kết với phần tử đó trong cây bóng và cung cấp cho bên ngoài một làn đường được chỉ định để vượt qua ranh giới bóng.
Dưới đây là ví dụ về cách tạo tiện ích thanh trượt tuỳ chỉnh và cho phép người dùng định kiểu cho con trỏ thanh trượt màu xanh dương:
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<div>
<div pseudo="x-slider-thumb"></div>' +
</div>
`;
</script>
Sử dụng biến CSS
Một cách hiệu quả để tạo các móc giao diện là thông qua Biến CSS. Về cơ bản, việc này sẽ tạo "phần giữ chỗ kiểu" để người dùng khác điền vào.
Hãy tưởng tượng một tác giả phần tử tuỳ chỉnh đánh dấu phần giữ chỗ biến trong Shadow DOM của họ. Một để định kiểu phông chữ của nút nội bộ và một để định kiểu màu của nút:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
Sau đó, trình nhúng của phần tử sẽ xác định các giá trị đó theo ý thích của họ. Có thể là để phù hợp với giao diện Comic Sans cực kỳ ngầu của trang riêng:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
Do cách Biến CSS kế thừa, mọi thứ đều ổn và hoạt động rất tốt! Toàn bộ hình ảnh sẽ có dạng như sau:
<style>
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
</style>
<div id="host">Host node</div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<style>
button {
color: var(--button-text-color, pink);
font-family: var(--button-font);
}
</style>
<content></content>
`;
</script>
Đặt lại kiểu
Các kiểu có thể kế thừa như phông chữ, màu sắc và chiều cao dòng tiếp tục ảnh hưởng đến các phần tử trong Shadow DOM. Tuy nhiên, để có được sự linh hoạt tối đa, Shadow DOM cung cấp cho chúng ta thuộc tính resetStyleInheritance
để kiểm soát những gì xảy ra ở ranh giới bóng.
Hãy coi đây là một cách để bắt đầu lại khi tạo một thành phần mới.
resetStyleInheritance
false
– Mặc định. Các thuộc tính CSS có thể kế thừa tiếp tục kế thừa.true
– đặt lại các thuộc tính có thể kế thừa thànhinitial
tại ranh giới.
Dưới đây là bản minh hoạ cho thấy cách thay đổi resetStyleInheritance
ảnh hưởng đến cây bóng:
<div>
<h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
`;
</script>
<div class="demoarea" style="width:225px;">
<div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
<button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>
<script>
var container = document.querySelector('#style-ex-inheritance');
var root = container.createShadowRoot();
//root.resetStyleInheritance = false;
root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';
document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
root.resetStyleInheritance = !root.resetStyleInheritance;
e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
});
</script>

Việc hiểu .resetStyleInheritance
sẽ khó khăn hơn một chút, chủ yếu là vì thuộc tính này chỉ ảnh hưởng đến các thuộc tính CSS có thể kế thừa. Nội dung này cho biết: khi bạn tìm kiếm một thuộc tính để kế thừa, ở ranh giới giữa trang và ShadowRoot, đừng kế thừa các giá trị từ máy chủ lưu trữ mà hãy sử dụng giá trị initial
(theo thông số kỹ thuật CSS).
Nếu bạn không chắc chắn về thuộc tính nào kế thừa trong CSS, hãy xem danh sách hữu ích này hoặc bật/tắt hộp đánh dấu "Hiện thuộc tính kế thừa" trong bảng điều khiển Thành phần.
Tạo kiểu cho các nút được phân phối
Nút phân phối là các phần tử hiển thị tại một điểm chèn (phần tử <content>
). Phần tử <content>
cho phép bạn chọn các nút từ DOM sáng và hiển thị các nút đó ở các vị trí được xác định trước trong Shadow DOM. Chúng không nằm trong Shadow DOM theo logic; chúng vẫn là phần tử con của phần tử lưu trữ. Điểm chèn chỉ là một thành phần hiển thị.
Các nút được phân phối giữ lại các kiểu từ tài liệu chính. Tức là các quy tắc kiểu từ trang chính sẽ tiếp tục áp dụng cho các phần tử, ngay cả khi các phần tử đó hiển thị tại một điểm chèn. Xin nhắc lại, các nút được phân phối vẫn nằm trong light dom theo logic và không di chuyển. Các thành phần này chỉ hiển thị ở nơi khác. Tuy nhiên, khi các nút được phân phối vào Shadow DOM, các nút này có thể sử dụng các kiểu bổ sung được xác định bên trong cây bóng.
Phần tử giả ::content
Các nút được phân phối là phần tử con của phần tử lưu trữ, vậy làm cách nào chúng ta có thể nhắm mục tiêu các nút đó từ bên trong Shadow DOM? Câu trả lời là phần tử giả ::content
CSS.
Đây là một cách để nhắm đến các nút DOM sáng đi qua một điểm chèn. Ví dụ:
::content > h3
định kiểu cho mọi thẻ h3
đi qua một điểm chèn.
Hãy xem ví dụ sau:
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
<style>
h3 { color: red; }
content[select="h3"]::content > h3 {
color: green;
}
::content section p {
text-decoration: underline;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
`;
</script>
Đặt lại kiểu tại các điểm chèn
Khi tạo ShadowRoot, bạn có thể đặt lại các kiểu kế thừa.
Các điểm chèn <content>
và <shadow>
cũng có tuỳ chọn này. Khi sử dụng các phần tử này, hãy đặt .resetStyleInheritance
trong JS hoặc sử dụng thuộc tính boolean reset-style-inheritance
trên chính phần tử đó.
Đối với điểm chèn ShadowRoot hoặc
<shadow>
:reset-style-inheritance
nghĩa là các thuộc tính CSS có thể kế thừa được đặt thànhinitial
tại máy chủ lưu trữ, trước khi các thuộc tính này truy cập vào nội dung bóng của bạn. Vị trí này được gọi là ranh giới trên.Đối với các điểm chèn
<content>
:reset-style-inheritance
có nghĩa là các thuộc tính CSS có thể kế thừa được đặt thànhinitial
trước khi các phần tử con của máy chủ lưu trữ được phân phối tại điểm chèn. Vị trí này được gọi là ranh giới dưới.
Kết luận
Là tác giả của các phần tử tuỳ chỉnh, chúng ta có rất nhiều lựa chọn để kiểm soát giao diện của nội dung. Shadow DOM là nền tảng cho thế giới mới mẻ này.
Shadow DOM cung cấp cho chúng ta tính năng đóng gói kiểu theo phạm vi và một phương tiện để cho phép nhiều (hoặc ít) thành phần bên ngoài như chúng ta chọn. Bằng cách xác định phần tử giả tuỳ chỉnh hoặc đưa vào phần giữ chỗ Biến CSS, tác giả có thể cung cấp cho bên thứ ba các móc định kiểu thuận tiện để tuỳ chỉnh thêm nội dung của họ. Tóm lại, tác giả web có toàn quyền kiểm soát cách nội dung của họ được trình bày.