מאז ומתמיד (במונחי CSS), עבדנו עם אשכול במובנים שונים. הסגנונות שלנו מורכבים מ'גיליון סגנון מדורג'. גם הבוררים שלנו פועלים ברצף. הם יכולים לנוע לצדדים. ברוב המקרים, הם יורדים למטה. אבל אף פעם לא למעלה. במשך שנים חלמנו על 'בורר הורים'. ועכשיו זה סוף סוף קורה! בצורת פסאודו בורר :has()
.
פסאודו-הקלאס :has()
ב-CSS מייצג אלמנט אם אחד מהסלקטורים שהועברו כפרמטרים תואם לאלמנט אחד לפחות.
אבל הוא לא רק בורר 'הורה'. זו דרך נחמדה לשווק אותו. הדרך הפחות אטרקטיבית היא הבורר 'סביבה מותנית'. אבל זה לא נשמע אותו דבר. מה לגבי הבורר 'משפחה'?
תמיכה בדפדפנים
לפני שנמשיך, כדאי לציין מהי תמיכת הדפדפנים. עדיין לא. אבל הוא מתקרב. עדיין אין תמיכה ב-Firefox, אבל היא בתוכנית. אבל הוא כבר זמין ב-Safari, והוא יושק בגרסה 105 של Chromium. בכל הדמואים שמופיעים במאמר הזה יצוין אם הם לא נתמכים בדפדפן שבו אתם משתמשים.
איך משתמשים ב- :has
איך זה נראה? בדוגמה הבאה מופיע קטע HTML עם שני רכיבים אחים עם הכיתה everybody
. איך בוחרים את זו שיש לה צאצא עם הכיתה a-good-time
?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
בעזרת :has()
, אפשר לעשות זאת באמצעות הקוד הבא ב-CSS.
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
הפעולה הזו בוחרת את המופע הראשון של .everybody
ומחילה animation
.
בדוגמה הזו, הרכיב עם המחלקה everybody
הוא היעד. התנאי הוא שיש לצאצא מחלקה עם המזהה a-good-time
.
<target>:has(<condition>) { <styles> }
אבל אפשר להרחיב את האפשרויות האלה הרבה יותר, כי :has()
פותח הרבה הזדמנויות. אפילו אלה שכנראה עדיין לא התגלו. כדאי לשקול כמה מהאפשרויות הבאות.
בוחרים רכיבי figure
שיש להם figcaption
ישיר.
css
figure:has(> figcaption) { ... }
בחירת רכיבי anchor
שאין להם צאצא SVG ישיר
css
a:not(:has(> svg)) { ... }
בחירת רכיבי label
שיש להם אח input
ישיר. כיוון התמונה לא נכון!
css
label:has(+ input) { … }
בחירת רכיבי article
שבהם לצאצא img
אין טקסט alt
css
article:has(img:not([alt])) { … }
בחירת רכיב documentElement
שבו יש מצב מסוים ב-DOM
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
בחירת מאגר התצוגה עם מספר אי זוגי של צאצאים
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
בחירת כל הפריטים ברשימה שמעליהם לא מרחפים
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
בחירת המאגר שמכיל רכיב <todo-list>
בהתאמה אישית
css
main:has(todo-list) { ... }
בחירת כל רכיב a
יחיד בתוך פסקאות שיש להן רכיב hr
אח ישיר
css
p:has(+ hr) a:only-child { … }
בחירת רכיב article
שבו מתקיימים כמה תנאים
css
article:has(>h1):has(>h2) { … }
שילוב של האפשרויות האלה. בוחרים article
כשכותרת מסוימת מופיעה אחרי כותרת משנית
css
article:has(> h1 + h2) { … }
בוחרים את :root
כשמפעילים מצבים אינטראקטיביים
css
:root:has(a:hover) { … }
בוחרים את הפסקה שמופיעה אחרי figure
שאין לה figcaption
css
figure:not(:has(figcaption)) + p { … }
אילו תרחישים לדוגמה מעניינים אפשר לחשוב עליהם ל-:has()
? הדבר המרתק הוא שהמודל הזה מעודד אתכם לשבור את המודל המנטלי שלכם. הוא גורם לכם לחשוב "האם אפשר לגשת לסגנונות האלה בצורה אחרת?".
דוגמאות
נציג כמה דוגמאות לאופן שבו אפשר להשתמש בה.
כרטיסים
צפייה בהדגמה של כרטיס קלאסי. אנחנו יכולים להציג כל מידע בכרטיס שלנו, למשל: שם, כתוביות או מדיה כלשהי. זה הכרטיס הבסיסי.
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
מה קורה כשרוצים להציג מדיה כלשהי? בעיצוב הזה, הכרטיס יכול להיות מחולק לשתי עמודות. קודם, אפשר ליצור סוג חדש שמייצג את ההתנהגות הזו, למשל card--with-media
או card--two-columns
. שמות הכיתות האלה לא רק קשה לזכור, אלא גם קשה לתחזק ולזכור אותם.
בעזרת :has()
תוכלו לזהות שכרטיס מכיל מדיה ולבצע את הפעולה המתאימה. אין צורך בשמות של כיתות של משתני ערך.
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
וגם אין צורך להשאיר אותו שם. אתם יכולים להיות יצירתיים בנושא הזה. איך כרטיס שמציג תוכן 'מומלץ' יכול להשתנות בהתאם לפריסה? קוד ה-CSS הזה יגרום לכרטיס המלצה להיות בגודל מלא של הפריסה ולהופיע בתחילת התצוגה של הרשת.
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
מה קורה אם כרטיס נבחר עם באנר זז כדי למשוך תשומת לב?
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
יש כל כך הרבה אפשרויות.
טפסים
מה לגבי טפסים? הם ידועים כקשים לסגנון. דוגמה לכך היא עיצוב של קלט והתוויות שלו. איך אנחנו מסמנים ששדה תקין, לדוגמה? בעזרת :has()
, זה הרבה יותר קל. אנחנו יכולים להתחבר לסוגי פסאודו הרלוונטיים של הטפסים, למשל :valid
ו-:invalid
.
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
אפשר לנסות את זה בדוגמה הבאה: נסו להזין ערכים חוקיים ולא חוקיים ולהעביר את המיקוד ביניהם.
אפשר גם להשתמש ב-:has()
כדי להציג ולהסתיר את הודעת השגיאה בשדה. ניקח את קבוצת השדות 'email' ונוסיף לה הודעת שגיאה.
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
כברירת מחדל, הודעת השגיאה מוסתרת.
.form-group__error {
display: none;
}
אבל כשהשדה הופך ל-:invalid
ולא ממוקד, אפשר להציג את ההודעה בלי צורך בשמות כיתה נוספים.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
אין סיבה שלא תוכלו להוסיף טוויסט אופנתי וטעים כשהמשתמשים יתכתבו עם הטופס שלכם. הנה דוגמה. שימו לב כשמזינים ערך תקין לאינטראקציה המיקרו. ערך :invalid
יגרום לקבוצת הטפסים לרעוד. אבל רק אם למשתמש אין העדפות תנועה.
תוכן
התייחסנו לכך בדוגמאות הקוד. אבל איך אפשר להשתמש ב-:has()
בתהליך העבודה עם המסמכים? הוא מאפשר לקבל רעיונות לגבי סגנון הגופן שאפשר להשתמש בו לצד מדיה, למשל.
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
הדוגמה הזו מכילה דמויות. אם לא מצוין figcaption
, התמונות צפות בתוך התוכן. כשיש figcaption
, הוא תופס את כל הרוחב ומקבל שוליים נוספים.
תגובה למצב
כדאי לנסות להפוך את הסגנונות שלך למגיבים למצב מסוים בתגי העיצוב שלנו. נבחן דוגמה עם סרגל הניווט הקלאסיק הנשלף. אם יש לכם לחצן שמאפשר לפתוח או לסגור את תפריט הניווט, יכול להיות שהוא משתמש במאפיין aria-expanded
. אפשר להשתמש ב-JavaScript כדי לעדכן את המאפיינים המתאימים. כשהערך של aria-expanded
הוא true
, משתמשים ב-:has()
כדי לזהות את המצב הזה ולעדכן את הסגנונות של סרגל הניווט הזזה. JavaScript מבצעת את התפקיד שלה, ו-CSS יכולה לעשות מה שהיא רוצה עם המידע הזה. אין צורך לשנות את הסימון או להוסיף שמות של כיתות נוספות וכו' (הערה: זו לא דוגמה מוכנה לייצור).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
האם אפשר להשתמש ב-:has כדי למנוע שגיאות של משתמשים?
מה המשותף לכל הדוגמאות האלה? מלבד העובדה שהן מראות דרכים להשתמש ב-:has()
, באף אחת מהן לא נדרשה שינוי של שמות הכיתות. כל אחד מהם הוסיף תוכן חדש ועדכן מאפיין. זוהי יתרון משמעותי של :has()
, כי היא יכולה לעזור לצמצם שגיאות של משתמשים. בעזרת :has()
, ה-CSS יכול לקחת על עצמו את האחריות להתאמה לשינויים ב-DOM. אין צורך לבצע פעולות גמישות עם שמות הכיתות ב-JavaScript, וכך יש פחות סיכוי לשגיאות מפתחים. כולנו טעינו פעם בשם של כיתה ונאלצנו לשמור אותה בחיפוש Object
.
זוהי מחשבה מעניינת, אבל האם היא מובילה אותנו לסימני markup נקיים יותר ולפחות קוד? פחות JavaScript, כי אנחנו מבצעים פחות התאמות של JavaScript. פחות HTML, כי אין יותר צורך בכיתות כמו card card--has-media
וכו'.
חשיבה מחוץ לקופסה
כפי שצוין למעלה, :has()
מעודדת אתכם לשבור את המודל המנטלי. זו הזדמנות לנסות דברים שונים. אחת הדרכים לנסות לדחוף את הגבולות היא ליצור מכניקה של משחקים באמצעות CSS בלבד. לדוגמה, אפשר ליצור מנגנון מבוסס-שלבים באמצעות טפסים ו-CSS.
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
וכך נפתחות אפשרויות מעניינות. אפשר להשתמש בזה כדי לעבור על טופס עם טרנספורמציות. הערה: מומלץ לצפות בהדגמה הזו בכרטיסייה נפרדת בדפדפן.
ואם אתם רוצים ליהנות, למה שלא תשחקו במשחק הקלאסי 'חבל החשמל'? קל יותר ליצור את המנגנון באמצעות :has()
. אם מעבירים את העכבר מעל החוט, המשחק נגמר. כן, אפשר ליצור חלק ממכניקות המשחק האלה באמצעות קומבינטורים של אחים (+
ו-~
). אבל אפשר להשתמש ב-:has()
כדי להשיג את אותן תוצאות בלי להשתמש ב'טריקים' מעניינים של סימון. הערה: מומלץ לצפות בהדגמה הזו בכרטיסייה נפרדת בדפדפן.
לא תצטרכו להשתמש בהן בקרוב בסביבת הייצור, אבל הן מדגישות דרכים שבהן אפשר להשתמש בפרימיטיב. למשל, אפשר לשרשר :has()
.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
ביצועים והגבלות
לפני שנפרד, מה אי אפשר לעשות עם :has()
? יש כמה הגבלות לגבי :has()
. הגורמים העיקריים לכך הם פגיעה בביצועים.
- אי אפשר
:has()
:has()
. אבל אפשר לשרשר:has()
.css :has(.a:has(.b)) { … }
- אין שימוש בפסאודו-רכיב בתוך
:has()
css :has(::after) { … } :has(::first-letter) { … }
- הגבלת השימוש ב-
:has()
בתוך פסאודו-קלאסות שמקבלות רק בוחרים מורכביםcss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- הגבלת השימוש ב-
:has()
אחרי רכיב פסאודוcss ::part(foo):has(:focus) { … }
- השימוש ב-
:visited
תמיד יהיה שגויcss :has(:visited) { … }
כדי לראות את מדדי הביצועים בפועל שקשורים ל-:has()
, אפשר לעיין בגליץ הזה. תודה ל-Byungwoo על התובנות והפרטים האלה לגבי ההטמעה.
זהו!
זה הזמן להתכונן ל-:has()
. כדאי לספר לחברים על העדכון הזה ולשתף את הפוסט הזה. הוא ישנה את האופן שבו אנחנו מתקרבים ל-CSS.
כל הדגמות זמינות באוסף הזה ב-CodePen.