:has(): انتخابگر خانواده

از زمان شروع (در اصطلاح 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) { ... } نیستند 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 موجود هستند.