CSS וסגנון
במאמר הזה נסביר על עוד כמה דברים מדהימים שאפשר לעשות עם Shadow DOM. הוא מבוסס על המושגים שמפורטים במאמר Shadow DOM 101. אם אתם מחפשים מבוא, כדאי לעיין במאמר הזה.
מבוא
בואו נודה בזה. אין שום דבר סקסי בתגי עיצוב ללא סגנון. למזלנו, האנשים המבריקים שמאחורי Web Components חזו את זה ולא השאירו אותנו ללא מענה. מודול ה-CSS Scoping מגדיר אפשרויות רבות לעיצוב תוכן בעץ צל.
אנקפסולציה של סגנונות
אחת מהתכונות המרכזיות של 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>
יש שתי תובנות מעניינות לגבי ההדגמה הזו:
- יש בדף הזה עוד תגי h3, אבל היחיד שתואמת לבורר h3, ולכן מקבלת סגנון אדום, הוא התג שב-ShadowRoot. שוב, סגנונות מוגדרים ברמת ההיקף כברירת מחדל.
- כללי סגנונות אחרים שהוגדרו בדף הזה ומיועדים לכותרות h3 לא משפיעים על התוכן שלי. הסיבה לכך היא שבוררי CSS לא חוצים את גבול ההצללה.
מה המסר של הסיפור? יש לנו אנקפסולציה של סגנון מהעולם החיצון. תודה 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 של רכיב, שיעניקו לו סגנון ייחודי על סמך ההקשר שלו.
תמיכה במספר סוגי מארחים מתוך root בצל אחד
שימוש נוסף ב-: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
אם לרכיב יש לפחות עץ צל אחד, פסאודו-הרכיב ::shadow
תואם לשורש הצל עצמו.
היא מאפשרת לכתוב בוחרים שמגדירים סגנון לקשרים פנימיים ב-DOM בצל של רכיב.
לדוגמה, אם רכיב מארח שורש צל, אפשר לכתוב #host::shadow span {}
כדי להגדיר סגנון לכל ה-spans שבתוך עץ הצל שלו.
<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
, תוך התעלמות מכל עצי הצללים:
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. בעיקרון, יוצרים 'placeholders של סגנון' שמשתמשים אחרים יכולים למלא.
נניח שכותב של רכיב מותאם אישית מסמנים placeholders של משתנים ב-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>
מאפשר לבחור צמתים מ-DOM האור ולייצר אותם במיקומים מוגדרים מראש ב-Shadow DOM. הם לא נמצאים באופן לוגי ב-Shadow DOM, אלא עדיין צאצאים של רכיב המארח. נקודות ההטמעה הן רק עניין של רינדור.
הצמתים המפוזרים שומרים את הסגנונות מהמסמך הראשי. כלומר, כללי הסגנון מהדף הראשי ממשיכים לחול על הרכיבים, גם כשהם עוברים עיבוד בנקודת ההוספה. שוב, צמתים מבוזרים עדיין נמצאים באופן לוגי בדומיין האור ולא זזים. הן פשוט מוצגות במקום אחר. עם זאת, כשהצומתים מופצים ל-Shadow DOM, הם יכולים לקבל סגנונות נוספים שמוגדרים בתוך עץ הצל.
רכיב פסאודו ::content
צמתים מבוזרים הם צאצאים של רכיב המארח, אז איך אפשר לטרגט אותם בתוך ה-Shadow DOM? התשובה היא פסאודו הרכיב ::content
ב-CSS.
זוהי דרך לטרגט צמתים של 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 מאפשר לנו לארוז סגנונות ברמת ההיקף ולאפשר כניסה של רכיבים מהעולם החיצוני לפי הצורך. באמצעות הגדרת רכיבי פסאודו בהתאמה אישית או הוספת placeholder של משתני CSS, המחברים יכולים לספק לצדדים שלישיים ווקים נוחים לעיצוב כדי להתאים אישית את התוכן שלהם עוד יותר. בסך הכול, לכותבי האתרים יש שליטה מלאה על האופן שבו התוכן שלהם מיוצג.