איך משתמשים ב-@scope כדי לבחור רכיבים רק בתוך עץ משנה מוגבל של ה-DOM
האמנות העדינה של כתיבת סלקטורים ב-CSS
כשכותבים בוחרים, יכול להיות שתמצאו את עצמכם נאבקים בין שני עולמות. מצד אחד, כדאי להיות ספציפיים לגבי הרכיבים שבוחרים. מצד שני, חשוב שעדיין יהיה קל לשנות את הסלקטורים, ושהם לא יהיו מקושרים באופן הדוק למבנה ה-DOM.
לדוגמה, כשרוצים לבחור באפשרות 'התמונה הראשית באזור התוכן של רכיב הכרטיס' – אפשרות בחירה לא ספציפית של רכיבים, סביר להניח שלא תרצו לכתוב סלקטור כמו .card > .content > img.hero
.
- לבורר הזה יש ספציפיות גבוהה למדי של
(0,3,1)
, ולכן קשה יותר לשנות אותו ככל שהקוד גדל. - הסתמכות על שילוב הצאצא הישיר מקושר למבנה ה-DOM. אם תגי העיצוב ישתנו, תצטרכו לשנות גם את ה-CSS.
עם זאת, לא כדאי לכתוב רק img
כסלקטור לרכיב הזה, כי הפעולה הזו תגרום לבחירה של כל רכיבי התמונות בדף.
לרוב, קשה למצוא את האיזון הנכון בנושא הזה. לאורך השנים, חלק מהמפתחים פיתחו פתרונות ודרכים לעקוף את המצבים האלה. לדוגמה:
- שיטות כמו BEM מכתיבות לתת לאלמנט הזה את הכיתה
card__img card__img--hero
כדי לשמור על רמת ספציפיות נמוכה, תוך שמירה על היכולת לבחור ספציפיות. - פתרונות מבוססי JavaScript, כמו CSS ממוקד או רכיבים מעוצבים, כותבים מחדש את כל הסלקטורים על ידי הוספת מחרוזות שנוצרות באופן אקראי – כמו
sc-596d7e0e-4
– לסלקטורים, כדי למנוע מהם לטרגט רכיבים בצד השני של הדף. - ספריות מסוימות אפילו מבטלות לחלוטין את הסלקטורים, ומחייבות להציב את טריגרי העיצוב ישירות בתגי העיצוב.
אבל מה אם לא צריך אף אחד מהם? מה אם CSS סיפק לכם דרך להיות ספציפיים למדי לגבי האלמנטים שבחרתם, בלי שתצטרכו לכתוב סלקטורים בעלי ספציפיות גבוהה או כאלה שמחוברים היטב ל-DOM שלכם? כאן נכנס לתמונה ה-@scope
, שמאפשר לבחור רכיבים רק בתוך עץ משנה של ה-DOM.
חדש: @scope
בעזרת @scope
אפשר להגביל את פוטנציאל החשיפה של הבוררים. כדי לעשות זאת, מגדירים את שורש ההיקף, שמגדיר את הגבול העליון של עץ המשנה שרוצים לטרגט. באמצעות קבוצת בסיס של היקף, כללי הסגנון הכלולים – שנקראים כללי סגנון היקף – יכולים לבחור רק מתוך עץ המשנה המוגבל של ה-DOM.
לדוגמה, כדי לטרגט רק את רכיבי <img>
ברכיב .card
, צריך להגדיר את .card
בתור הרמה הבסיסית (root) של הכלל @scope
.
@scope (.card) {
img {
border-color: green;
}
}
כלל הסגנון ברמת ההיקף img { … }
יכול לבחור בפועל רק רכיבים מסוג <img>
שנמצאים בהיקף ההרשאות של הרכיב .card
שנמצאה בו התאמה.
כדי למנוע את הבחירה של רכיבי <img>
בתוך אזור התוכן של הכרטיס (.card__content
), אפשר להפוך את הבורר img
לספציפי יותר. דרך נוספת לעשות זאת היא להשתמש בעובדה שכלל at-rule @scope
מקבל גם הגבלת היקף שקובעת את הגבול התחתון.
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
כלל הסגנון ברמת ההיקף הזה מטרגט רק רכיבי <img>
שממוקמים בין רכיבי .card
לבין רכיבי .card__content
בעץ האב. סוג כזה של הגדרת היקף – עם גבול עליון ותחתון – נקרא לעיתים קרובות היקף בצורת עיגול עם חור.
הבורר :scope
כברירת מחדל, כל כללי הסגנון ברמת ההיקף יחסיים לשורש ההיקף. אפשר גם לטרגט את רכיב הבסיס של ההיקף עצמו. לשם כך, משתמשים בבורר :scope
.
@scope (.card) {
:scope {
/* Selects the matched .card itself */
}
img {
/* Selects img elements that are a child of .card */
}
}
לבוררים בתוך כללי סגנון ברמת ההיקף מתווסף באופן משתמע :scope
. אם רוצים, אפשר להוסיף את :scope
בתחילת השם כדי לציין זאת בבירור. לחלופין, אפשר להוסיף את הסלקטור &
בתחילת הקוד, באמצעות עיצוב עץ ב-CSS.
@scope (.card) {
img {
/* Selects img elements that are a child of .card */
}
:scope img {
/* Also selects img elements that are a child of .card */
}
& img {
/* Also selects img elements that are a child of .card */
}
}
אפשר להשתמש בפסאודו-סיווג :scope
במגבלה ברמת ההיקף כדי לדרוש קשר ספציפי לשורש ההיקף:
/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }
מגבלת היקף יכולה גם להפנות לרכיבים שמחוץ לרמה הבסיסית (root) שלהם באמצעות :scope
. לדוגמה:
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
שימו לב שכללי הסגנון של ההיקף לא יכולים לסמן בתו בריחה (escape) את עץ המשנה. בחירות כמו :scope + p
לא תקינות כי המערכת מנסה לבחור רכיבים שלא נכללים במסגרת הזו.
@scope
וספציפיות
הסלקטורים שבהם משתמשים בהקדמה של @scope
לא משפיעים על מידת הספציפיות של הבוררים הכלולים. בדוגמה הבאה, הספציפיות של הבורר img
היא עדיין (0,0,1)
.
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
…
}
}
הספציפיות של :scope
היא כמו זו של פסאודו-כיתה רגילה, כלומר (0,1,0)
.
@scope (#sidebar) {
:scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
…
}
}
בדוגמה הבאה, באופן פנימי, ה-&
נכתב מחדש לסלקטור שמשמש לשורש ההיקף, עטוף בתוך סלקטור :is()
. בסופו של דבר, הדפדפן ישתמש ב-:is(#sidebar, .card) img
כסלקטור לביצוע ההתאמה. התהליך הזה נקרא הסרת סוכר.
@scope (#sidebar, .card) {
& img { /* desugars to `:is(#sidebar, .card) img` */
…
}
}
מכיוון ש-&
עובר הסרה של סוכר באמצעות :is()
, הספציפיות של &
מחושבת לפי כללי הספציפיות של :is()
: הספציפיות של &
היא זו של הארגומנט הספציפי ביותר שלו.
בהתאם לדוגמה הזו, הספציפיות של :is(#sidebar, .card)
היא זו של הארגומנט הספציפי ביותר שלה, כלומר #sidebar
, ולכן היא הופכת ל-(1,0,0)
. משלבים את זה עם הספציפיות של img
– שהיא (0,0,1)
– ומקבלים את הערך (1,0,1)
כספציפיות של הסלקטור המורכב כולו.
@scope (#sidebar, .card) {
& img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
…
}
}
ההבדל בין :scope
לבין &
בתוך @scope
בנוסף להבדלים באופן שבו מחושב מידת הספציפיות, יש הבדל נוסף בין :scope
לבין &
: הערך :scope
מייצג את הבסיס של היקף ההתאמה, ואילו הערך &
מייצג את הבורר ששימש להתאמה לבסיס של היקף ההתאמה.
לכן ניתן להשתמש ב-&
מספר פעמים. השיטה הזו שונה מ-:scope
, שבו אפשר להשתמש רק פעם אחת, כי לא ניתן להתאים שורש היקף בתוך שורש היקף.
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
היקף ללא Prelude
כשכותבים סגנונות מוטבעים עם הרכיב <style>
, אפשר להגדיר את היקף כללי הסגנון לרכיב ההורה המקיף של הרכיב <style>
אם לא לציין בסיס בהיקף. כדי לעשות זאת, משמיטים את הקדמה של @scope
.
<div class="card">
<div class="card__header">
<style>
@scope {
img {
border-color: green;
}
}
</style>
<h1>Card Title</h1>
<img src="…" height="32" class="hero">
</div>
<div class="card__content">
<p><img src="…" height="32"></p>
</div>
</div>
בדוגמה שלמעלה, הכללים ברמת ההיקף מטרגטים רק אלמנטים בתוך div
עם שם הכיתה card__header
, כי div
הוא רכיב ההורה של רכיב <style>
.
@scope במפל
בתוך Cascade של CSS, @scope
מוסיף גם קריטריון חדש: scoping proximity. השלב מופיע אחרי רמת הספציפיות אבל לפני סדר ההופעה.
כשמשווים בין הצהרות שמופיעות בכללי סגנון עם שורשי היקף שונים, ההצהרה עם הכי פחות קפיצות דור או קפיצות בין רכיבי אחים בין שורש ההיקף לבין הנושא של כלל הסגנון ברמת ההיקף מנצחת.
השלב החדש הזה שימושי כשרוצים להטמיע כמה וריאציות של רכיב. בדוגמה הבאה עדיין לא נעשה שימוש ב-@scope
:
<style>
.light { background: #ccc; }
.dark { background: #333; }
.light a { color: black; }
.dark a { color: white; }
</style>
<div class="light">
<p><a href="#">What color am I?</a></p>
<div class="dark">
<p><a href="#">What about me?</a></p>
<div class="light">
<p><a href="#">Am I the same as the first?</a></p>
</div>
</div>
</div>
כשמציגים את קטע ה-Markup הזה, הקישור השלישי יהיה white
במקום black
, למרות שהוא צאצא של div
עם הכיתה .light
שחלה עליו. הסיבה לכך היא קריטריון סדר ההופעה שבו המערכת משתמשת כדי לקבוע את הזוכה. המערכת מזהה ש-.dark a
הוצהר לאחרונה, ולכן הוא יזכה לפי הכלל .light a
הבעיה נפתרה עכשיו באמצעות קריטריון הקרבה ברמת ההיקף:
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
רמת הספציפיות של שני הסלקטורים ברמת a
זהה, ולכן קריטריון הקירבה לקביעת היקף נכנס לתוקף. הוא שוקל את שני הבוררים לפי הקרבה לשורש ההיקף שלהם. לרכיב השלישי a
, יש רק קפיצה אחת לשורש ההיקף .light
, אבל שתי קפיצות לשורש ההיקף .dark
. לכן, הבורר a
ב-.light
יזכה.
הערה אחרונה: בידוד הבורר, לא בידוד סגנון
חשוב לציין ש-@scope
מגביל את היקף הבחירה של הבוררים, אבל הוא לא מספק בידוד של סגנונות. נכסים שעברו בירושה לצאצאים עדיין יירשו, מעבר לגבול התחתון של @scope
. אחד מהמאפיינים האלה הוא color
. כשמגדירים את ה-one בתוך היקף של עוגת סופגנייה, ה-color
עדיין יורש לצאצאים בתוך החור של העוגה.
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
בדוגמה שלמעלה, לרכיב .card__content
ולצאצאיו יש צבע hotpink
כי הם יורשים את הערך מ-.card
.
(תמונת השער של rustam burkhanov ב-Unsplash)