از زمان شروع (در اصطلاح CSS)، ما با یک آبشار در معانی مختلف کار کردهایم. سبک های ما یک "برگ سبک آبشاری" تشکیل می دهند. و انتخاب کنندگان ما نیز آبشار می شوند. آنها می توانند به پهلو بروند. در بیشتر موارد به سمت پایین می روند. اما هرگز به سمت بالا. برای سالها، ما درباره یک "انتخاب کننده والدین" خیال پردازی کرده ایم. و حالا بالاخره در راه است! به شکل شبه انتخابگر :has()
.
شبه کلاس :has()
CSS عنصری را نشان می دهد اگر هر یک از انتخابگرهای ارسال شده به عنوان پارامتر حداقل با یک عنصر مطابقت داشته باشد.
اما، این چیزی بیش از یک انتخابگر "والد" است. این یک راه خوب برای بازاریابی آن است. راه نه چندان جذاب ممکن است انتخابگر "محیط شرطی" باشد. اما این حلقه کاملاً مشابه آن نیست. انتخابگر «خانواده» چطور؟
پشتیبانی مرورگر
قبل از اینکه جلوتر برویم، لازم است به پشتیبانی مرورگر اشاره کنیم. هنوز کاملاً آنجا نیست. اما، دارد نزدیک تر می شود. هنوز فایرفاکس پشتیبانی نمی شود، در نقشه راه است. اما در حال حاضر در Safari موجود است و قرار است در Chromium 105 منتشر شود. همه دموهای این مقاله به شما میگویند که آیا در مرورگر مورد استفاده پشتیبانی نمیشوند یا خیر.
نحوه استفاده :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) { … }
documentElement
article
انتخاب کنید که در آن یک img
اول متن alt
ندارد css article:has(img:not([alt])) { … }
the 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()
برای نمایش و پنهان کردن پیام خطای یک فیلد استفاده کنید. گروه فیلد "ایمیل" ما را بردارید و یک پیام خطا به آن اضافه کنید.
<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
استفاده کند. جاوا اسکریپت می تواند برای به روز رسانی ویژگی های مناسب استفاده شود. هنگامی که aria-expanded
true
است، از :has()
برای تشخیص این مورد استفاده کنید و استایلها را برای نوار کشویی بهروزرسانی کنید. جاوا اسکریپت وظیفه خود را انجام می دهد و CSS می تواند آنچه را که می خواهد با آن اطلاعات انجام دهد. نیازی به تغییر نشانه گذاری در اطراف یا اضافه کردن نام کلاس های اضافی و غیره نیست (توجه: این یک نمونه آماده تولید نیست).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
آیا :برای جلوگیری از خطای کاربر کمکی دارد؟
همه این مثال ها چه چیزی مشترک دارند؟ جدا از این واقعیت که آنها راه هایی برای استفاده :has()
نشان می دهند، هیچ یک از آنها نیاز به تغییر نام کلاس ها نداشتند. آنها هر کدام محتوای جدیدی را وارد کردند و یک ویژگی را به روز کردند. این مزیت بزرگ :has()
است که می تواند به کاهش خطای کاربر کمک کند. با :has()
CSS قادر است مسئولیت تنظیم تغییرات در DOM را بر عهده بگیرد. نیازی نیست نام کلاسها را در جاوا اسکریپت جابهجا کنید و پتانسیل کمتری برای خطای توسعهدهنده ایجاد کنید. همه ما زمانی که نام یک کلاس را تایپ می کنیم آنجا بوده ایم و مجبور بوده ایم آنها را در جستجوی Object
نگه داریم.
این یک فکر جالب است و آیا ما را به سمت نشانه گذاری تمیزتر و کد کمتر سوق می دهد؟ جاوا اسکریپت کمتری وجود دارد زیرا تنظیمات جاوا اسکریپت زیادی را انجام نمی دهیم. 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;
}
و این احتمالات جالبی را باز می کند. می توانید از آن برای عبور از یک فرم با تبدیل استفاده کنید. توجه داشته باشید، این نسخه آزمایشی بهتر است در یک برگه مرورگر جداگانه مشاهده شود.
و برای سرگرمی، بازی کلاسیک buzz wire چطور؟ ایجاد مکانیک با :has()
آسانتر است. اگر سیم روی آن شناور شود، بازی تمام شده است. بله، ما میتوانیم برخی از این مکانیکهای بازی را با چیزهایی مانند ترکیبکنندههای خواهر و برادر ( +
و ~
) ایجاد کنیم. اما، :has()
راهی برای دستیابی به همان نتایج بدون نیاز به استفاده از "ترفندهای" نشانه گذاری جالب است. توجه داشته باشید، این نسخه آزمایشی بهتر است در یک برگه مرورگر جداگانه مشاهده شود.
اگرچه به این زودی اینها را وارد تولید نخواهید کرد، اما راههایی را نشان میدهند که در آنها میتوانید از بدوی استفاده کنید. مانند اینکه بتوان یک :has()
را زنجیر کرد.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
عملکرد و محدودیت ها
قبل از رفتن، با :has()
چه کاری نمی توانید انجام دهید؟ محدودیت هایی با :has()
وجود دارد. موارد اصلی به دلیل بازدیدهای عملکردی بوجود می آیند.
- شما نمی توانید
:has()
a: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()
، این اشکال را بررسی کنید. برای به اشتراک گذاشتن این بینش ها و جزئیات در مورد پیاده سازی، به بیونگ وو اعتبار دارد.
همین!
برای :has()
آماده شوید. در مورد آن به دوستان خود بگویید و این پست را به اشتراک بگذارید، این یک تغییر بازی برای نحوه برخورد ما با CSS خواهد بود.
همه دموها در این مجموعه CodePen موجود هستند.