CSS และการจัดรูปแบบ
บทความนี้จะกล่าวถึงสิ่งอัศจรรย์เพิ่มเติมที่คุณทําได้โดยใช้ Shadow DOM ซึ่งจะต่อยอดจากแนวคิดที่กล่าวถึงใน Shadow DOM 101 หากต้องการทราบข้อมูลเบื้องต้น โปรดอ่านบทความดังกล่าว
บทนำ
ว่ากันตามจริง มาร์กอัปที่ไม่มีการจัดรูปแบบนั้นไม่น่าดึงดูดใจ เราโชคดีที่มีทีมที่ยอดเยี่ยมที่อยู่เบื้องหลัง Web Components คาดการณ์เรื่องนี้ไว้และไม่ได้ปล่อยให้เรารอ โมดูลการกำหนดขอบเขต CSS จะกำหนดตัวเลือกมากมายสำหรับการจัดสไตล์เนื้อหาในทรีเงา
การห่อหุ้มสไตล์
ฟีเจอร์หลักอย่างหนึ่งของ Shadow DOM คือขอบเขตเงา เครื่องมือนี้มีคุณสมบัติดีๆ มากมาย แต่ข้อดีอย่างหนึ่งที่โดดเด่นที่สุดคือมีการจัดแพ็กเกจสไตล์ให้ฟรี กล่าวโดยละเอียดคือ
<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>
ข้อสังเกตที่น่าสนใจ 2 ข้อเกี่ยวกับการสาธิตนี้ ได้แก่
- มี h3 อื่นๆ ในหน้านี้ แต่มีเพียงรายการเดียวที่ตรงกับตัวเลือก h3 และได้รับการจัดสไตล์เป็นสีแดง ซึ่งก็คือรายการที่อยู่ใน ShadowRoot อีกครั้ง รูปแบบที่มีขอบเขตโดยค่าเริ่มต้น
- กฎสไตล์อื่นๆ ที่กําหนดไว้ในหน้านี้ซึ่งกำหนดเป้าหมายเป็น h3 จะไม่ส่งผลต่อเนื้อหาของฉัน เนื่องจากตัวเลือกจะไม่ข้ามขอบเขตเงา
สรุปเรื่องราว เรามีการห่อหุ้มสไตล์จากโลกภายนอก ขอบคุณ Shadow DOM
จัดแต่งองค์ประกอบโฮสต์
:host
ช่วยให้คุณเลือกและจัดรูปแบบองค์ประกอบที่โฮสต์ทรีเงาได้ ดังนี้
<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>
ข้อควรระวังอย่างหนึ่งคือกฎในหน้าหลักมีความเฉพาะเจาะจงมากกว่ากฎ :host
ที่กําหนดไว้ในองค์ประกอบ แต่มีความเฉพาะเจาะจงน้อยกว่าแอตทริบิวต์ style
ที่กําหนดไว้ในองค์ประกอบโฮสต์ ซึ่งช่วยให้ผู้ใช้ลบล้างการจัดรูปแบบของคุณจากภายนอกได้
นอกจากนี้ :host
ยังใช้ได้ในบริบทของ ShadowRoot เท่านั้น คุณจึงใช้นอก Shadow DOM ไม่ได้
รูปแบบฟังก์ชันของ :host(<selector>)
ช่วยให้คุณกําหนดเป้าหมายองค์ประกอบโฮสต์ได้หากตรงกับ <selector>
ตัวอย่าง - จับคู่เฉพาะในกรณีที่องค์ประกอบเองมีคลาส .different
(เช่น <x-foo class="different"></x-foo>
)
:host(.different) {
...
}
การตอบสนองต่อสถานะของผู้ใช้
กรณีการใช้งานที่พบบ่อยของ :host
คือเมื่อคุณสร้างองค์ประกอบที่กําหนดเองและต้องการตอบสนองต่อสถานะต่างๆ ของผู้ใช้ (:hover, :focus, :active ฯลฯ)
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
การกำหนดธีมองค์ประกอบ
คลาสจำลอง :host-context(<selector>)
จะจับคู่กับองค์ประกอบโฮสต์หากองค์ประกอบนั้นหรือบรรพบุรุษขององค์ประกอบตรงกับ <selector>
การใช้งาน :host-context()
ที่พบบ่อยคือการกำหนดธีมให้กับองค์ประกอบโดยอิงตามองค์ประกอบรอบๆ ตัวอย่างเช่น ผู้ใช้จํานวนมากใช้ธีมโดยการใช้คลาสกับ <html>
หรือ <body>
<body class="different">
<x-foo></x-foo>
</body>
คุณ:host-context(.different)
เพื่อจัดสไตล์ <x-foo>
ได้เมื่อเป็นองค์ประกอบที่สืบทอดมาจากองค์ประกอบที่มีคลาส .different
ดังนี้
:host-context(.different) {
color: red;
}
ซึ่งจะช่วยให้คุณรวมกฎสไตล์ไว้ใน Shadow DOM ขององค์ประกอบที่จะกำหนดสไตล์ให้องค์ประกอบนั้นๆ ได้อย่างโดดเด่น โดยอิงตามบริบท
รองรับโฮสต์หลายประเภทจากภายในรูทเงาเดียว
อีกการใช้งานหนึ่งของ :host
คือในกรณีที่คุณสร้างไลบรารีธีมและต้องการรองรับการจัดสไตล์องค์ประกอบโฮสต์หลายประเภทจากภายใน 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>. */
}
จัดแต่งสไตล์ภายในของ Shadow DOM จากภายนอก
องค์ประกอบจำลอง ::shadow
และตัวรวม /deep/
เปรียบเสมือนการมีดาบ Vorpal ที่ทรงอำนาจของ CSS
ซึ่งช่วยให้เจาะผ่านขอบเขตของ Shadow DOM เพื่อจัดรูปแบบองค์ประกอบภายในต้นไม้เงาได้
องค์ประกอบจำลอง ::shadow
หากองค์ประกอบมีต้นไม้เงาอย่างน้อย 1 รายการ พิวโซอิเล็มน์ ::shadow
จะจับคู่กับรูทเงา
ซึ่งช่วยให้คุณเขียนตัวเลือกที่กำหนดสไตล์โหนดภายใน DOM เงาขององค์ประกอบได้
เช่น หากองค์ประกอบโฮสต์รูทเงา คุณสามารถเขียน #host::shadow span {}
เพื่อจัดสไตล์สแปนทั้งหมดภายในต้นไม้เงาได้
<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>
ตัวอย่าง (องค์ประกอบที่กําหนดเอง) - <x-tabs>
มีองค์ประกอบย่อย <x-panel>
ใน Shadow DOM แต่ละแผงจะโฮสต์ต้นไม้เงาของตัวเองซึ่งมีส่วนหัว h2
หากต้องการจัดรูปแบบส่วนหัวเหล่านั้นจากหน้าหลัก ให้เขียนดังนี้
x-tabs::shadow x-panel::shadow h2 {
...
}
ตัวคอมบิเนเตอร์ /deep/
ตัวคอมบิเนเตอร์ /deep/
คล้ายกับ ::shadow
แต่มีประสิทธิภาพมากกว่า โดยจะไม่สนใจขอบเขตเงาทั้งหมดและข้ามไปยังต้นไม้เงาจำนวนเท่าใดก็ได้ กล่าวโดยย่อคือ /deep/
ช่วยให้คุณเจาะลึกองค์ประกอบและกำหนดเป้าหมายโหนดใดก็ได้
ตัวรวม /deep/
มีประโยชน์อย่างยิ่งในโลกขององค์ประกอบที่กําหนดเองซึ่งมี Shadow DOM หลายระดับอยู่ทั่วไป ตัวอย่างที่เห็นได้ชัดคือการวางซ้อนองค์ประกอบที่กำหนดเองหลายรายการ (แต่ละรายการโฮสต์ทรีเงาของตัวเอง) หรือการสร้างองค์ประกอบที่รับค่ามาจากองค์ประกอบอื่นโดยใช้ <shadow>
ตัวอย่าง (องค์ประกอบที่กําหนดเอง) - เลือกองค์ประกอบ <x-panel>
ทั้งหมดที่เป็นรายการสืบทอดของ <x-tabs>
ในทุกที่ในต้นไม้
x-tabs /deep/ x-panel {
...
}
ตัวอย่าง - จัดรูปแบบองค์ประกอบทั้งหมดที่มีคลาส .library-theme
ได้ทุกที่ในทรีเงา
body /deep/ .library-theme {
...
}
การใช้งาน querySelector()
คอมบิเนเตอร์จะเปิดทรีเงาสําหรับการเรียกดูตัวเลือกเช่นเดียวกับที่ .shadowRoot
เปิดทรีเงาสําหรับการเรียกดู DOM
แทนที่จะเขียนเงื่อนไขแบบซ้อนกัน คุณเขียนคำสั่งเดียวได้ดังนี้
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
การจัดรูปแบบองค์ประกอบเนทีฟ
การควบคุม HTML ดั้งเดิมจัดรูปแบบได้ยาก หลายคนจึงเลิกใช้และสร้างแอปขึ้นมาเอง แต่ ::shadow
และ /deep/
จะช่วยให้คุณจัดสไตล์องค์ประกอบใดก็ได้ในแพลตฟอร์มเว็บที่ใช้ Shadow DOM ตัวอย่างที่ดี ได้แก่ <input>
และ <video>
video /deep/ input[type="range"] {
background: hotpink;
}
การสร้างฮุกสไตล์
การปรับแต่งทำได้ดี ในบางกรณี คุณอาจต้องการเจาะรูในเกราะการจัดสไตล์ของ Shadow และสร้างฮุกเพื่อให้ผู้อื่นจัดสไตล์
การใช้ ::shadow และ /deep/
/deep/
มีความสามารถมากมาย ซึ่งช่วยให้ผู้เขียนคอมโพเนนต์มีวิธีกำหนดองค์ประกอบแต่ละรายการให้ปรับแต่งสไตล์ได้ หรือกำหนดองค์ประกอบจำนวนมากให้ปรับธีมได้
ตัวอย่าง - จัดรูปแบบองค์ประกอบทั้งหมดที่มีคลาส .library-theme
โดยละเว้น Shadow Tree ทั้งหมด
body /deep/ .library-theme {
...
}
การใช้องค์ประกอบจำลองที่กําหนดเอง
ทั้ง WebKit และ Firefox กำหนดองค์ประกอบจำลองสำหรับการจัดสไตล์ชิ้นส่วนภายในขององค์ประกอบเบราว์เซอร์เนทีฟ ตัวอย่างที่ดีคือ input[type=range]
คุณจัดรูปแบบแถบเลื่อน <span style="color:blue">blue</span>
ได้โดยกำหนดเป้าหมาย ::-webkit-slider-thumb
ดังนี้
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
เช่นเดียวกับที่เบราว์เซอร์มีฮุกการจัดสไตล์สำหรับองค์ประกอบภายในบางอย่าง ผู้เขียนเนื้อหา Shadow DOM สามารถกำหนดให้องค์ประกอบบางอย่างจัดสไตล์ได้โดยบุคคลภายนอก ซึ่งทำได้ผ่านองค์ประกอบจำลองที่กำหนดเอง
คุณสามารถกำหนดองค์ประกอบเป็นองค์ประกอบจำลองที่กำหนดเองได้โดยใช้แอตทริบิวต์ pseudo
ค่าหรือชื่อขององค์ประกอบต้องขึ้นต้นด้วย "x-" ซึ่งจะสร้างการเชื่อมโยงกับองค์ประกอบนั้นในทรีเงาและกำหนดช่องทางให้บุคคลภายนอกข้ามขอบเขตเงา
ต่อไปนี้เป็นตัวอย่างการสร้างวิดเจ็ตแถบเลื่อนที่กําหนดเองและอนุญาตให้ผู้ใช้จัดรูปแบบแถบเลื่อนเป็นสีฟ้า
<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>
การใช้ตัวแปร CSS
วิธีสร้างฮุกสำหรับธีมที่มีประสิทธิภาพคือการใช้ตัวแปร CSS หลักๆ แล้วก็คือการสร้าง "ตัวยึดตำแหน่งสไตล์" ให้ผู้ใช้รายอื่นกรอก
ลองจินตนาการถึงผู้เขียนองค์ประกอบที่กําหนดเองซึ่งทำเครื่องหมายตัวยึดตําแหน่งตัวแปรใน Shadow DOM 1 รายการสำหรับกำหนดสไตล์แบบอักษรของปุ่มภายใน และอีก 1 รายการสำหรับกำหนดสี
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
จากนั้นผู้ฝังองค์ประกอบจะกําหนดค่าเหล่านั้นตามต้องการ อาจเป็นเพราะธีม Comic Sans ที่เจ๋งสุดๆ ของหน้าเว็บ
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
เนื่องจากตัวแปร CSS มีการรับค่ามา ทุกอย่างจึงเรียบร้อยดีและทำงานได้อย่างยอดเยี่ยม ภาพรวมทั้งหมดจะมีลักษณะดังนี้
<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>
รีเซ็ตรูปแบบ
ลักษณะที่รับช่วงมา เช่น แบบอักษร สี และระยะบรรทัดจะยังคงส่งผลต่อองค์ประกอบใน Shadow DOM อย่างไรก็ตาม Shadow DOM มีพร็อพเพอร์ตี้ resetStyleInheritance
ให้เราควบคุมสิ่งที่เกิดขึ้นที่ขอบเขตของ Shadow เพื่อความยืดหยุ่นสูงสุด
โปรดคิดว่าการดําเนินการนี้เป็นการเริ่มต้นใหม่เมื่อสร้างคอมโพเนนต์ใหม่
resetStyleInheritance
false
- ค่าเริ่มต้น พร็อพเพอร์ตี้ CSS ที่รับค่าได้จะรับค่าต่อไปtrue
- รีเซ็ตพร็อพเพอร์ตี้ที่รับค่าได้โดยค่าเริ่มต้นเป็นinitial
ที่ขอบเขต
ด้านล่างนี้คือตัวอย่างที่แสดงให้เห็นว่าการเปลี่ยนแปลง resetStyleInheritance
ส่งผลต่อต้นไม้เงาอย่างไร
<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>
การทําความเข้าใจ .resetStyleInheritance
นั้นทําได้ยากกว่าเล็กน้อย เนื่องจากมีผลกระทบต่อพร็อพเพอร์ตี้ CSS ที่รับค่าได้เท่านั้น ข้อความระบุว่า: เมื่อคุณกําลังมองหาพร็อพเพอร์ตี้ที่จะรับค่ามา ที่ขอบเขตระหว่างหน้าเว็บกับ ShadowRoot อย่ารับค่าจากโฮสต์ แต่ให้ใช้ค่า initial
แทน (ตามข้อกําหนดของ CSS)
หากไม่แน่ใจว่าพร็อพเพอร์ตี้ใดรับช่วงใน CSS โปรดดูรายการที่มีประโยชน์นี้ หรือสลับช่องทำเครื่องหมาย "แสดงรายการที่รับช่วง" ในแผงองค์ประกอบ
จัดสไตล์โหนดที่กระจาย
โหนดที่กระจายคือองค์ประกอบที่แสดงผลที่จุดแทรก (องค์ประกอบ <content>
) องค์ประกอบ <content>
ช่วยให้คุณเลือกโหนดจาก Light DOM และแสดงผลโหนดเหล่านั้นในตำแหน่งที่กําหนดไว้ล่วงหน้าใน Shadow DOM ได้ องค์ประกอบเหล่านี้ไม่ได้อยู่ใน Shadow DOM ในทางตรรกะ แต่ยังคงเป็นองค์ประกอบย่อยขององค์ประกอบโฮสต์ จุดแทรกเป็นเพียงเรื่องของการแสดงผล
โหนดที่กระจายจะเก็บรูปแบบจากเอกสารหลักไว้ กล่าวคือ กฎสไตล์จากหน้าหลักจะมีผลกับองค์ประกอบต่อไป แม้ว่าจะแสดงผลที่จุดแทรกก็ตาม อีกครั้ง โหนดที่กระจายจะยังคงอยู่ในโดเมน Light และไม่ย้ายไปไหน แต่จะแสดงผลที่อื่น อย่างไรก็ตาม เมื่อมีการกระจายโหนดไปยัง Shadow DOM โหนดเหล่านั้นจะใช้สไตล์เพิ่มเติมที่กําหนดไว้ในต้นไม้เงาได้
องค์ประกอบจำลอง ::content
โหนดที่กระจายอยู่เป็นองค์ประกอบย่อยขององค์ประกอบโฮสต์ เราจะกำหนดเป้าหมายโหนดเหล่านั้นจากภายใน Shadow DOM ได้อย่างไร คำตอบคือองค์ประกอบสมมติ ::content
ของ CSS
นี่เป็นวิธีกำหนดเป้าหมายโหนด Light DOM ที่ผ่านจุดแทรก เช่น
::content > h3
จัดรูปแบบแท็ก h3
ทั้งหมดที่ผ่านจุดแทรก
มาดูตัวอย่างกัน
<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>
รีเซ็ตรูปแบบที่จุดแทรก
เมื่อสร้าง ShadowRoot คุณจะมีตัวเลือกในการรีเซ็ตสไตล์ที่รับค่ามา
จุดแทรก <content>
และ <shadow>
ก็มีตัวเลือกนี้ด้วย เมื่อใช้องค์ประกอบเหล่านี้ ให้ตั้งค่า .resetStyleInheritance
ใน JS หรือใช้แอตทริบิวต์บูลีน reset-style-inheritance
ในองค์ประกอบนั้นๆ
สําหรับจุดแทรก ShadowRoot หรือ
<shadow>
:reset-style-inheritance
หมายความว่าระบบตั้งค่าพร็อพเพอร์ตี้ CSS ที่รับค่าได้เป็นinitial
ที่โฮสต์ ก่อนที่จะนำไปใช้กับเนื้อหาเงา ตำแหน่งนี้เรียกว่า "ขอบเขตบน"สำหรับจุดแทรก
<content>
:reset-style-inheritance
หมายถึงรับค่าได้ ระบบจะตั้งค่าพร็อพเพอร์ตี้ CSS เป็นinitial
ก่อนกระจายรายการย่อยของโฮสต์ ณ จุดแทรก ตำแหน่งนี้เรียกว่าขอบเขตล่าง
บทสรุป
ในฐานะผู้เขียนองค์ประกอบที่กำหนดเอง เราจึงมีตัวเลือกมากมายในการควบคุมรูปลักษณ์ของเนื้อหา Shadow DOM เป็นพื้นฐานของยุคใหม่นี้
Shadow DOM ช่วยให้เราห่อหุ้มสไตล์แบบมีขอบเขตและมีวิธีรับข้อมูลภายนอกได้มาก (หรือน้อย) เท่าที่ต้องการ การกําหนดองค์ประกอบจำลองที่กําหนดเองหรือใส่ตัวยึดตําแหน่งตัวแปร CSS ช่วยให้ผู้เขียนระบุฮุกการจัดสไตล์ที่สะดวกสําหรับบุคคลที่สามเพื่อปรับแต่งเนื้อหาเพิ่มเติมได้ โดยสรุปแล้ว ผู้เขียนเว็บจะควบคุมการแสดงเนื้อหาของตนเองได้อย่างเต็มที่