איך משתמשים ב-@scope כדי לבחור רכיבים רק בתוך עץ משנה מוגבל של ה-DOM
האמנות העדינה של כתיבת סלקטורים ב-CSS
כשכותבים בוחרים, יכול להיות שתמצאו את עצמכם נמצאים בין שני עולמות. מצד אחד, כדאי להיות ספציפיים לגבי הרכיבים שבוחרים. מצד שני, חשוב שעדיין יהיה קל לשנות את הסלקטורים, ושהם לא יהיו מקושרים באופן הדוק למבנה ה-DOM.
לדוגמה, אם רוצים לבחור את 'תמונת ה-Hero באזור התוכן של רכיב הכרטיס' – בחירה ספציפית למדי של רכיב – סביר להניח שלא כדאי לכתוב סלקטור כמו .card > .content > img.hero
.
- לבורר הזה יש ספציפיות גבוהה למדי של
(0,3,1)
, ולכן קשה לשנות אותו ככל שהקוד מתארך. - השימוש ב-combinator של הצאצא הישיר גורם לקישור הדוק למבנה ה-DOM. אם ה-Markup ישתנה, תצטרכו לשנות גם את ה-CSS.
עם זאת, לא כדאי לכתוב רק img
כסלקטור לרכיב הזה, כי הפעולה הזו תגרום לבחירה של כל רכיבי התמונות בדף.
לרוב, קשה למצוא את האיזון הנכון בנושא הזה. במשך השנים, מפתחים מסוימים הגיעו לפתרונות ולדרכים לעקיפת הבעיה שיעזרו לכם במצבים כאלה. לדוגמה:
- שיטות כמו BEM מכתיבות לתת לאלמנט הזה את הכיתה
card__img card__img--hero
כדי לשמור על רמת ספציפיות נמוכה, תוך שמירה על היכולת לבחור ספציפיות. - פתרונות מבוססי JavaScript, כמו CSS ממוקד או רכיבים מעוצבים, כותבים מחדש את כל הסלקטורים על ידי הוספת מחרוזות שנוצרות באופן אקראי – כמו
sc-596d7e0e-4
– לסלקטורים, כדי למנוע מהם לטרגט רכיבים בצד השני של הדף. - בחלק מהספריות הסלקטורים אפילו לא קיימים, וצריך להוסיף את הטריגרים של עיצוב הרכיבים ישירות לתווית ה-HTML.
אבל מה אם לא צריך אף אחד מהם? מה אם CSS היה נותן לכם דרך להיות ספציפיים למדי לגבי הרכיבים שבחרתם, בלי שתצטרכו לכתוב סלקטורים ספציפיים מאוד או כאלה שמקושרים בצורה הדוקה ל-DOM? כאן נכנס לתמונה ה-@scope
, שמאפשר לבחור רכיבים רק בתוך עץ משנה של ה-DOM.
חדש: @scope
בעזרת @scope
אפשר להגביל את פוטנציאל החשיפה של הבוררים. כדי לעשות זאת, מגדירים את שורש ההיקף, שמגדיר את הגבול העליון של עץ המשנה שרוצים לטרגט. כשמשתמשים בקבוצת שורש להגדרת היקף, כללי הסגנון הכלולים – שנקראים כללי סגנון מוגדרי היקף – יכולים לבחור רק מתוך עץ המשנה המוגבל הזה של ה-DOM.
לדוגמה, כדי לטרגט רק את הרכיבים <img>
ברכיב .card
, מגדירים את .card
כשורש ההיקף של כלל at @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) { ... }
אפשר גם להשתמש ב-:scope
כדי להפנות למגבלה ברמת ההיקף לרכיבים מחוץ לשורש ההיקף שלה. לדוגמה:
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
חשוב לזכור שכללי הסגנון ברמת ההיקף לא יכולים לצאת מהעץ המשני. בחירות כמו :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)