Container Queries؛ این بار عمیق‌تر

تو فصل ۱۴ وقتی درباره‌ی Responsive Web Design صحبت کردیم، به‌طور مختصر با container query ها آشنا شدیم و دیدیم که برخلاف @media، این‌ها بر اساس سایز والد کار می‌کنن، نه سایز viewport. حالا که چندین فصل پیشرفته‌تر (Flexbox، Grid، Logical Properties) رو گذروندیم، وقتشه یه بار دیگه با دید کامل‌تری بریم سراغش و ببینیم دقیقاً چطور تو یه پروژه‌ی واقعی ازش استفاده کنیم.

یادآوری سریع: چرا اصلاً به container query نیاز داریم؟

مشکلی که container query حلش می‌کنه اینه: یه کامپوننت مثل کارت محصول، ممکنه تو یه صفحه هم داخل یه سایدبار باریک قرار بگیره، هم داخل یه گرید سه‌ستونه‌ی عریض. با @media، شما فقط می‌تونید بر اساس عرض کل viewport تصمیم بگیرید، در حالی که این کامپوننت لازمه بر اساس فضایی که واقعاً داخلش قرار گرفته (که ممکنه ربطی به سایز کل صفحه نداشته باشه) خودش رو تنظیم کنه. این دقیقاً همون چیزیه که container query حل می‌کنه.

یادآوری سینتکس پایه

.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  .card {
    display: flex;
    gap: 16px;
  }
}

دو مرحله همیشه لازمه: اول باید به یه المنت والد بگید که «container» باشه (با container-type)، بعد تو @container بر اساس سایز همون والد شرط بذارید.

container-type: چه مقادیری داره و فرقشون چیه؟

مقداررفتار
inline-sizeپرکاربردترین حالت؛ فقط عرض (inline dimension) رو پیگیری می‌کنه
sizeهم عرض و هم ارتفاع رو پیگیری می‌کنه
normalپیش‌فرض؛ المنت اصلاً container به‌حساب نمیاد

نکته‌ی مهمی که تو فصل ۱۴ کامل بهش نپرداختیم: وقتی container-type: size می‌ذارید، مرورگر مجبور می‌شه یه containment سخت‌گیرانه‌تر اعمال کنه، که باعث می‌شه المنت دیگه نتونه بر اساس محتوای فرزندهاش سایز بگیره (یعنی باید یه ارتفاع مشخص یا قابل‌محاسبه بهش بدید). به همین دلیل، تو اکثر موارد واقعی، inline-size انتخاب امن‌تر و رایج‌تریه.

container-name: چرا و کِی لازمه؟

اگه فقط یه container تو صفحه دارید، لازم نیست اسمی بهش بدید؛ @container (min-width: 400px) بدون اسم، به نزدیک‌ترین container والد اشاره می‌کنه. اما وقتی چند container تو در تو دارید (مثلاً یه کارت داخل یه سایدبار که خودش هم container هست)، بدون اسم مشخص، ممکنه شرط شما به container اشتباهی (نزدیک‌ترین، نه اونی که واقعاً می‌خواید) اعمال بشه:

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.card-wrapper {
  container-type: inline-size;
  container-name: card;
}

/* دقیقاً به container با اسم card اشاره می‌کنه، نه sidebar */
@container card (min-width: 400px) {
  .card-title {
    font-size: 1.4rem;
  }
}

یه نکته‌ی کاربردی: می‌شه container-type و container-name رو با shorthand container تو یه خط نوشت:

.card-wrapper {
  container: card / inline-size;
}

container query units: واحدهایی که نسبت به container حساب می‌شن

همون‌طور که vw و vh نسبت به viewport حساب می‌شن، یه سری واحد مشابه هم داریم که نسبت به container حساب می‌شن:

واحدمعادل با
cqw۱٪ از عرض container
cqh۱٪ از ارتفاع container
cqi۱٪ از inline-size (معمولاً معادل عرض)
cqb۱٪ از block-size (معمولاً معادل ارتفاع)
.card-title {
  font-size: clamp(1rem, 5cqi, 2rem);
}

این یعنی سایز فونت عنوان، به‌جای این‌که به viewport وابسته باشه، مستقیماً با عرض همون container که داخلشه، تغییر می‌کنه؛ چیزی که با vw اصلاً امکان‌پذیر نبود.

Style Queries: شرط گذاشتن بر اساس مقدار یک custom property

یه قابلیت جدیدتر که تو فصل ۱۴ بهش اشاره نکردیم، style query هست. با این، می‌تونید بر اساس مقدار یه custom property (که تو فصل ۱۶ باهاش آشنا شدیم) روی container، شرط بذارید، نه فقط سایزش:

.theme-wrapper {
  --theme: dark;
}

@container style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: white;
  }
}

پشتیبانی مرورگرها از style query هنوز محدودتر از size query هست، پس قبل از استفاده‌ی جدی تو پروژه، حتماً چک کنید.

نکته مهم: یه محدودیت مهم که باید بدونید: یک المنت نمی‌تونه هم‌زمان container باشه و خودش هم داخل همون container query که تعریفش کرده استایل بگیره. یعنی container-type رو معمولاً روی یه wrapper می‌ذارید، نه مستقیم روی المنتی که می‌خواید استایلش رو عوض کنید.

اشتباه رایج: قاطی کردن container query با media query تو ترتیب کد

یه اشتباه رایج اینه که فکر کنید ترتیب نوشتن @container نسبت به بقیه‌ی قوانین مثل specificity عادی عمل می‌کنه. در واقع، @container فقط تعیین می‌کنه یه قانون «کِی فعال بشه» (بر اساس سایز container)؛ وقتی فعاله، همچنان قوانین عادی cascade و specificity بین خودش و بقیه‌ی قوانین اعمال می‌شه. پس اگه یه قانون خارج از @container با specificity بالاتر باشه، همچنان می‌تونه قانون داخل @container رو override کنه.

جمع‌بندی

Container Queries اجازه می‌ده استایل یه کامپوننت بر اساس سایز والد واقعیش تغییر کنه، نه سایز کل viewport؛ این باعث می‌شه کامپوننت‌ها واقعاً قابل‌استفاده‌ی دوباره (reusable) باشن، فارغ از این‌که کجای صفحه قرار می‌گیرن. نکات کلیدی: برای اکثر موارد از inline-size استفاده کنید، وقتی چند container تو در تو دارید حتماً container-name بذارید، و واحدهای cqw/cqi رو برای سایزبندی نسبی به container در نظر داشته باشید.