אפשר להגביל את פוטנציאל החשיפה של הסלקטורים בעזרת CSS @scope at-rule

ללמוד איך להשתמש ב- @scope כדי לבחור רכיבים רק בתוך עץ משנה מוגבל של ה-DOM.

תמיכה בדפדפן

  • 118
  • 118
  • x
  • x

האומנות העדינה של כתיבת סלקטורים ב-CSS

כשאתם כותבים סלקטורים, אתם עלולים להיקלע לשני עולמות. מצד אחד, אתם רוצים להיות ספציפיים למדי לגבי הרכיבים שבחרתם. מצד שני, אתם רוצים שהסלקטורים שלכם ימשיכו להיות קלים לשינוי ולא יהיו מקושרים באופן הדוק למבנה ה-DOM.

לדוגמה: אם רוצים לבחור באפשרות 'התמונה הראשית באזור התוכן של רכיב הכרטיס' (זו בחירה די ספציפית של רכיבים), סביר להניח שלא תרצו לכתוב בורר כמו .card > .content > img.hero.

  • לבורר הזה יש ספציפיות גבוהה למדי של (0,3,1), ולכן קשה יותר לשנות אותו ככל שהקוד גדל.
  • על ידי הסתמכות על שילוב הצאצא הישיר, הוא מקושר באופן הדוק למבנה ה-DOM. אם תגי העיצוב ישתנו, תצטרכו לשנות גם את ה-CSS.

עם זאת, לא כדאי גם לכתוב img בתור הבורר של אותו רכיב, כי הפעולה הזו תגרום לבחירת כל רכיבי התמונה בדף.

בדרך כלל קשה למצוא את האיזון הנכון. לאורך השנים, מפתחים מסוימים פיתחו פתרונות ופתרונות עקיפים שיעזרו לך במצבים כאלה. לדוגמה:

  • שיטות כמו BEM קובעות שאתם נותנים לרכיב הזה סיווג של card__img card__img--hero כדי לשמור על רמת ספציפיות נמוכה, ובמקביל לאפשר לכם להיות ספציפיים במה שאתם בוחרים.
  • פתרונות מבוססי JavaScript, כמו Scoped CSS או Style Components, כותבים מחדש את כל הסלקטורים באמצעות הוספה של מחרוזות שנוצרות באופן אקראי – כמו sc-596d7e0e-4 – לסלקטורים שלכם, כדי למנוע מהם לטרגט לרכיבים בצד השני של הדף.
  • ספריות מסוימות אפילו מבטלות את הסלקטורים לגמרי ומחייבות אתכם להציב את טריגרי הסגנון ישירות בתג העיצוב עצמו.

אבל מה היה קורה אם לא היית צריך אף אחד מאלה? מה יקרה אם CSS יאפשר לכם להיות ספציפיים למדי לגבי הרכיבים שאתם בוחרים, בלי שתצטרכו לכתוב סלקטורים ספציפיים מאוד או סלקטורים שקשורים באופן הדוק ל-DOM? כאן נכנסת לתמונה @scope, והוא מאפשר לכם לבחור רכיבים רק מתוך עץ משנה של ה-DOM.

נעים להכיר: @scope

עם @scope אפשר להגביל את פוטנציאל החשיפה של הבוררים. כדי לעשות זאת, מגדירים את שורש ההיקף שקובע את הגבול העליון של עץ המשנה שאליו רוצים לטרגט. באמצעות קבוצת שורש של היקף, כללי הסגנון הכלולים – בעלי שם כללי סגנון – יכולים לבחור רק מתוך עץ המשנה המוגבל של ה-DOM.

לדוגמה, כדי לטרגט רק לרכיבי <img> ברכיב .card, צריך להגדיר את .card בתור השורש של השיטה @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

כברירת מחדל, כל כללי הסגנון להיקף מסוים יחסיים לבסיס של ההיקף. ניתן גם לטרגט לרכיב הבסיס של ההיקף עצמו. לשם כך, צריך להשתמש בבורר :scope.

@scope (.card) {
    :scope {
        /* Selects the matched .card itself */
    }
    img {
       /* Selects img elements that are a child of .card */
    }
}

בוררים שבתוך כללי סגנון בהיקף כוללים בדרך כלל הוספה של :scope מראש. אם רוצים, אפשר להתנסח בצורה מפורשת על ידי צירוף :scope מראש. לחלופין, אפשר להוסיף את הסלקטור & בתחילת הסלקטור, דרך CSS Nesting.

@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 */
  }
  :root :root { /* ❌ 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 Ciscade

בתוך דירוג CSS, @scope גם מוסיף קריטריון חדש: קרבה להיקף. השלב הוא אחרי ספציפיות אבל לפני סדר ההופעה.

הצגה חזותית של דירוג CSS.

בהתאם למפרט:

כשמשווים בין הצהרות שמופיעות בכללי סגנון עם רמות היקף שונות, ההצהרה עם המספר הקטן ביותר של פעולות צאצא של דור או אח מנצחת בין השורש בהיקף ההרשאות לבין הנושא של כלל הסגנון בהיקף ההרשאות.

שלב חדש זה שימושי כאשר ניתן להציב כמה וריאציות של רכיב בתוך רכיב. למשל, עוד לא נעשה שימוש ב-@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 מוגדרים לאותה רמת ספציפיות, קריטריון הקרבה להגדרת ההיקף נכנס לפעולה. הפונקציה משקללת את שני הבוררים לפי הקרבה לשורש ההיקף שלהם. עבור רכיב ה-a השלישי, יש לבצע רק צעד אחד לשורש ההיקפי של .light, אבל שניים לבסיס ה-.dark. לכן, הבורר a ב.light ינצח.

הערה סוגרת: בידוד הבורר, לא בידוד הסגנון

הערה חשובה אחת: המדיניות @scope מגבילה את פוטנציאל החשיפה של הבוררים, ולא מאפשרת בידוד של סגנון. נכסים שיורשים למטה לילדים עדיין יורשים, מעבר לגבול התחתון של ה@scope. אחד המאפיינים האלה הוא המאפיין color. כשמצהירים על כך שהשדה נמצא בתוך היקף של טבעת, ה-color עדיין עובר בירושה לילדים שנמצאים בתוך החור של הסופגנייה.

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}

בדוגמה שלמעלה, לרכיב .card__content ולצאצאים שלו יש צבע hotpink כי הם יורשים את הערך מ-.card.

(תמונת שער של rustam burkhanov on Unwash)