CSS และการจัดรูปแบบ
บทความนี้จะกล่าวถึงสิ่งมหัศจรรย์ที่คุณทำได้โดยใช้ Shadow DOM ซึ่งต่อยอดจากแนวคิดที่กล่าวถึงใน Shadow DOM 101 ถ้ากำลังมองหาบทนำ โปรดดูบทความนั้น
เกริ่นนำ
ว่ากันตามจริง มาร์กอัปที่ไม่มีสไตล์ไม่มีความเซ็กซี่ โชคดีสำหรับเรา บุคลากรชั้นยอดที่อยู่เบื้องหลังคอมโพเนนต์เว็บ มองเห็นสิ่งนี้และทำให้พวกเรารอไม่ไหว โมดูลขอบเขตของ 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 รายการเดียวที่ตรงกับตัวเลือก 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>
Gocha หนึ่งคือกฎในหน้าหลักมีความจำเพาะสูงกว่ากฎ :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 เพื่อจัดสไตล์องค์ประกอบภายในต้นไม้เงา
องค์ประกอบ Pseudo-shadow ::shadow
หากองค์ประกอบมีต้นไม้เงาอย่างน้อย 1 ต้น องค์ประกอบจำลอง ::shadow
จะตรงกับรากเงานั้นๆ
ซึ่งจะช่วยให้คุณเขียนตัวเลือกที่จัดรูปแบบโหนดภายในไปยัง Shadow Dom ขององค์ประกอบได้
เช่น หากองค์ประกอบหนึ่งโฮสต์ Shadow Root อยู่ คุณเขียน #host::shadow span {}
เพื่อจัดรูปแบบช่วงทั้งหมดภายใน Shadow Tree ได้
<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 และ /deep/
/deep/
มีพลังงานมากมาย ช่วยให้ผู้เขียนคอมโพเนนต์กำหนดวิธีกำหนด
องค์ประกอบแต่ละรายการเป็นแบบปรับแต่งสไตล์ได้ หรือกำหนดองค์ประกอบหลายรายการตามธีมได้
ตัวอย่าง - จัดรูปแบบองค์ประกอบทั้งหมดที่มีคลาส .library-theme
โดยละเว้นต้นไม้เงาทั้งหมด ดังนี้
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 ของตน ปุ่มหนึ่งใช้สำหรับจัดรูปแบบแบบอักษรของปุ่มภายในและอีกปุ่มหนึ่งสำหรับสีปุ่ม
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
แก่เราในการควบคุมสิ่งที่เกิดขึ้นที่ขอบเขตเงา
ซึ่งเปรียบได้กับการเริ่มต้นใหม่เมื่อสร้างคอมโพเนนต์ใหม่
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 อย่างสมเหตุสมผล แต่ยังคงเป็นองค์ประกอบย่อยขององค์ประกอบโฮสต์ จุดแทรกเป็นเพียงตัวแสดงผลเท่านั้น
โหนดแบบกระจายจะเก็บรูปแบบจากเอกสารหลัก กล่าวคือ กฎของรูปแบบจากหน้าหลักจะยังมีผลกับองค์ประกอบนั้นๆ ต่อไป แม้ว่าจะแสดงผลที่จุดแทรกก็ตาม เช่นเดียวกัน โหนดแบบกระจายจะยังคงอยู่ในขอบเขตแสงและไม่เคลื่อนที่ รูปภาพเหล่านี้จะแสดงผลในที่อื่นๆ อย่างไรก็ตาม เมื่อกระจายโหนดไปยัง Shadow DOM โหนดจะใช้รูปแบบเพิ่มเติมที่กำหนดไว้ใน Shadow Tree ได้
::content องค์ประกอบเทียม
โหนดแบบกระจายคือโหนดย่อยขององค์ประกอบโฮสต์ เราจะกำหนดเป้าหมายโหนดจากภายใน Shadow DOM ได้อย่างไร คำตอบคือองค์ประกอบเทียมของ CSS ::content
เป็นวิธีการกำหนดเป้าหมายโหนด 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 ช่วยให้ผู้เขียนสามารถใส่ฮุกการจัดรูปแบบที่สะดวกของบุคคลที่สามเพื่อปรับแต่งเนื้อหาของตนเพิ่มเติมได้ โดยสรุปแล้ว ผู้เขียนในเว็บจะควบคุมวิธีนำเสนอเนื้อหาของตนได้อย่างเต็มที่