ابزارهای مدرن و منطقی برای Selectorهای پیچیده

در طول این فصل، چند بار به Selectorهای تابعی مثل :not() اشاره کردیم، بدون این‌که کاملاً بازشون کنیم. حالا وقتشه این چهار Selector قدرتمند و کاملاً مدرن رو با جزئیات کامل ببینیم؛ ابزارهایی که به ما اجازه می‌دن Selectorهای پیچیده رو کوتاه‌تر، خواناتر و منطقی‌تر بنویسیم.

is(): ترکیب چند Selector در یک شرط

:is() یک لیست از Selectorها رو به‌عنوان ورودی می‌گیره و هر عنصری که با حداقل یکی از اون‌ها مطابقت داشته باشه رو انتخاب می‌کنه.

مشکل بدون :is()

article h1,
article h2,
article h3 {
  font-family: "Georgia", serif;
}

این کد کار می‌کنه، ولی article سه بار تکرار شده. با :is() می‌شه همین رو خیلی کوتاه‌تر نوشت:

article :is(h1, h2, h3) {
  font-family: "Georgia", serif;
}

این Selector دقیقاً همون نتیجه رو می‌ده: هر h1، h2 یا h3ای که داخل article باشه.

where(): مثل is() ولی بدون اضافه کردن Specificity

:where() از نظر عملکرد دقیقاً مثل :is() کار می‌کنه؛ تنها و مهم‌ترین تفاوتش اینه که Specificity‌ش همیشه صفره، صرف‌نظر از اینکه چه Selectorهایی داخلش نوشته شده باشن.

:where(h1, h2, h3) {
  margin-top: 0;
}

این Rule، Specificity برابر (0,0,0,0) داره؛ یعنی حتی یک Type Selector ساده مثل h1 به‌تنهایی می‌تونه به‌راحتی این استایل رو Override کنه.

تفاوت کلیدی is() و where() در Specificity

:is(#header, .box) p {
  color: blue;
}

:where(#header, .box) p {
  color: green;
}

Specificity برای :is()، برابر Specificity بالاترین آرگومان داخلش محاسبه می‌شه؛ یعنی چون #header یک ID است، کل Selector اول وزن ID می‌گیره: (0,1,0,1). اما :where()، صرف‌نظر از اینکه #header داخلش باشه، همیشه وزن صفر داره؛ پس Specificity کل Selector دوم فقط از p میاد: (0,0,0,1).

این تفاوت خیلی مهمه، مخصوصاً وقتی می‌خوایم یه استایل پایه بنویسیم که به‌راحتی توسط استایل‌های بعدی Override بشه، بدون این‌که نگران Specificity بالا باشیم.

not(): معکوس کردن یک شرط

:not() عناصری رو انتخاب می‌کنه که با Selector داخلش مطابقت نداشته باشن.

p:not(.intro) {
  color: gray;
}
<p class="intro">این پاراگراف مستثنی است.</p>
<p>این پاراگراف رنگ گرفته می‌شود.</p>

در این مثال، هر پاراگرافی که کلاس intro نداشته باشه رنگ خاکستری می‌گیره؛ پاراگراف اول که این کلاس رو داره، از این قانون مستثنی می‌شه.

استفاده از چند Selector داخل not()

input:not([type="submit"], [type="button"]) {
  border: 1px solid #ccc;
}

این Selector همه inputها رو، به‌جز اونایی که نوع‌شون submit یا button هست، انتخاب می‌کنه. در نسخه‌های قدیمی‌تر CSS، :not() فقط یک Selector قبول می‌کرد؛ استاندارد فعلی اجازه می‌ده لیستی از Selectorها با کاما داخلش نوشته بشه.

has(): انتخاب بر اساس فرزندان

:has() یکی از قدرتمندترین اضافه‌های اخیر CSS‌ه؛ برخلاف تمام Selectorهایی که تا الان دیدیم و بر اساس خود عنصر یا Siblingهاش کار می‌کردن، :has() اجازه می‌ده یک عنصر رو بر اساس وجود یک فرزند یا رابطه خاص داخلش انتخاب کنیم.

article:has(img) {
  border: 1px solid #ddd;
  padding: 16px;
}
<article>
  <h2>مقاله با تصویر</h2>
  <img src="photo.jpg" alt="نمونه">
</article>

<article>
  <h2>مقاله بدون تصویر</h2>
</article>

در این مثال، فقط articleای که یک img در هر عمقی داخلش داره، بوردر و padding می‌گیره؛ article دوم که تصویری نداره، بدون تغییر می‌مونه.

ترکیب has() با Combinatorها

label:has(+ input:invalid) {
  color: crimson;
}

این Selector، هر labelای رو که بلافاصله قبل از یک input نامعتبر قرار داره، قرمز می‌کنه. این ترکیب قبلاً کاملاً غیرممکن بود، چون CSS تا پیش از :has()، هیچ راهی برای «انتخاب والد بر اساس فرزند» یا «انتخاب بر اساس Sibling بعدی» نداشت.

یک مثال معروف: انتخاب والد بر اساس وضعیت فرزند

.form-group:has(input:focus) {
  background-color: #f0f8ff;
}

این Rule می‌گه: «اگه داخل یک .form-group، هر inputی در حال فوکوس باشه، خود .form-group رنگ پس‌زمینه بگیره». این دقیقاً همون کاربردیه که تا قبل از :has()، فقط با جاوااسکریپت قابل انجام بود.

ترکیب هر چهار Selector با هم

:is(article, section):has(:where(h2, h3):not(.no-style)) {
  margin-bottom: 24px;
}

بیایم این Selector پیچیده رو تیکه‌تیکه بخونیم: «هر article یا sectionای (:is()) که داخلش یک h2 یا h3 (:where()) وجود داشته باشه (:has())، به شرطی که اون تیتر کلاس no-style نداشته باشه (:not())». هرچند این مثال کمی پیچیده‌ست، ولی نشون می‌ده این چهار ابزار چطور می‌تونن با هم ترکیب بشن.

جدول مقایسه

Selector عملکرد تأثیر روی Specificity
:is() مطابقت با هر یک از چند Selector وزن بالاترین آرگومان داخلی
:where() مثل is()، ولی برای گروه‌بندی بدون افزایش وزن همیشه صفر
:not() معکوس کردن یک یا چند شرط وزن بالاترین آرگومان داخلی
:has() انتخاب بر اساس وجود فرزند یا رابطه داخلی وزن بالاترین آرگومان داخلی

نکته درباره وضعیت پشتیبانی مرورگرها

:has() نسبت به :is()، :where() و :not() جدیدتره و در مرورگرهای قدیمی‌تر پشتیبانی نمی‌شه. پیش از استفاده از این Selector در پروژه‌های واقعی، بررسی وضعیت پشتیبانی مرورگرهای هدف پروژه ضروریه.

نکات مهم

  • :is() و :where() از نظر عملکرد یکسان هستن؛ تنها تفاوت‌شون در محاسبه Specificity‌ست.
  • :not() در نسخه فعلی CSS، امکان پذیرفتن چند Selector با کاما رو داره.
  • :has() اولین و تنها Selectoریه که اجازه می‌ده والد بر اساس فرزندش انتخاب بشه؛ قابلیتی که پیش از این فقط با جاوااسکریپت ممکن بود.

اشتباهات رایج

  • استفاده از :is() در جایی که Specificity پایین مدنظره، در حالی که :where() گزینه مناسب‌تری بود.
  • فراموش کردن بررسی پشتیبانی مرورگر برای :has() پیش از استفاده در پروژه‌های واقعی.
  • نوشتن Selectorهای بیش‌ازحد تودرتو و پیچیده با ترکیب این چهار تابع، که خوانایی کد رو کاهش می‌ده.

جمع‌بندی

:is() و :where() امکان گروه‌بندی چند Selector در یک شرط واحد رو می‌دن، با این تفاوت که دومی هیچ‌وقت Specificity اضافه نمی‌کنه. :not() یک یا چند Selector رو معکوس می‌کنه، و :has() اولین ابزاریه که اجازه می‌ده یک عنصر بر اساس وجود فرزند یا رابطه داخلی‌اش انتخاب بشه. این چهار Selector تابعی، از مهم‌ترین ابزارهای CSS مدرن برای نوشتن کدهای کوتاه‌تر و منطقی‌تر محسوب می‌شن.