איך משתמשים ב- @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
לספציפי יותר. דרך נוספת לעשות זאת היא להשתמש בעובדה שהכלל @scope
מקבל גם מגבלת היקף שקובעת את הגבול התחתון.
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
כלל הסגנון בהיקף הזה מטרגט רק רכיבי <img>
שנמצאים בין רכיבי .card
ל-.card__content
בעץ האב. סוג זה של גבולות גזרה – עם גבול עליון ותחתון – נקרא בדרך כלל היקף של טבעת
הבורר :scope
כברירת מחדל, כל כללי הסגנון ברמת ההיקף יהיו יחסיים לרמה הבסיסית (root) של ההיקף. אפשר גם לטרגט את רכיב הבסיס של ההיקף עצמו. לשם כך, צריך להשתמש בבורר :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) */
…
}
}
בדוגמה הבאה, באופן פנימי, הבורר &
נכתב מחדש בבורר שמשמש את הרמה הבסיסית (root) של ההיקף, והוא מוקף בבורר :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
מייצג את הרמה הבסיסית (root) התואמת, ואילו &
מייצג את הבורר שמשמש להתאמה של הרמה הבסיסית (root) של ההיקף.
לכן ניתן להשתמש ב-&
מספר פעמים. השיטה הזו שונה מ-:scope
, שבו אפשר להשתמש רק פעם אחת, כי לא ניתן להתאים שורש היקף בתוך שורש היקף.
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
היקף ללא הקדמה
כשכותבים סגנונות מוטבעים עם הרכיב <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 in the cascade
בתוך דירוג ה-CSS, חברת @scope
מוסיפה גם קריטריון חדש: קרבה בהיקף. השלב מתבצע אחרי ספציפיות אבל לפני סדר ההופעה.
בהתאם למפרט:
כשמשווים בין הצהרות שמופיעות בכללי סגנון עם שורשים עם היקפים שונים, ההצהרה עם הצעדים הכי פחות הגנרטיביים או רכיבי אח, בין השורש של ההיקף לבין הנושא של כלל הסגנון של ההיקף, זוכה.
השלב החדש הזה שימושי כאשר מניחים מספר וריאציות של רכיב. לדוגמה, שעדיין לא משתמשת ב-@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>
כשמציגים את קטע העיצוב הקטן הזה, הקישור השלישי יהיה 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
זהה, ולכן קריטריון הקירבה לקביעת היקף נכנס לתוקף. שני הסלקטורים משוקללים לפי הקירבה לרמה הבסיסית (root) של ההיקף שלהם. עבור רכיב a
השלישי, יש רק מעבר אחד לרמה הבסיסית (root) של ההיקף .light
אבל שתיים לרמה של .dark
. לכן, הבורר a
ב.light
ינצח.
הערה אחרונה: בידוד הבורר, לא בידוד סגנון
חשוב לציין: @scope
מגביל את פוטנציאל החשיפה של הסלקטורים, ולא מאפשר בידוד סגנון. נכסים שעברו בירושה לצאצאים עדיין יירשו, מעבר לגבול התחתון של @scope
. אחד המאפיינים האלה הוא color
. אם מצהירים על כך שאחד בתוך היקף של סופגניה, color
עדיין עובר בירושה לילדים בתוך החור של הדונאטס.
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
בדוגמה שלמעלה, לרכיב .card__content
ולצאצאים שלו יש צבע hotpink
כי הם יורשים את הערך מ-.card
.
(תמונת השער של rustam burkhanov ב-Unbounce)