واکنش‌گرایی نسبت به والد، نه صفحه‌نمایش: Container Queries و @container

در تمام درس‌های قبل این فصل، شرط تغییر استایل، همیشه عرض کل Viewport بود (با @media). اما یک محدودیت مهم وجود داره: چه اتفاقی می‌افته وقتی یک کامپوننت (مثلاً یک کارت)، در جاهای مختلف صفحه، با عرض‌های کاملاً متفاوتی (مثلاً یک‌بار در یک Sidebar باریک، یک‌بار در یک بخش عریض) استفاده بشه؟ Media Query، فقط عرض کل صفحه رو می‌بینه، نه عرض واقعی همون کامپوننت. Container Queries، دقیقاً همین مشکل رو حل می‌کنن.

مشکل: یک کامپوننت، در عرض‌های مختلف والد

.card {
  display: flex;
  flex-direction: column;
}

@media (min-width: 768px) {
  .card {
    flex-direction: row;
  }
}
<div class="wide-section">
  <div class="card">کارت در بخش عریض</div>
</div>

<aside class="narrow-sidebar">
  <div class="card">همین کارت، ولی در سایدبار باریک</div>
</aside>

در این مثال، با Media Query سنتی، هر دو کارت، دقیقاً یک رفتار یکسان دارن؛ چون هر دو، بر اساس عرض کل Viewport تصمیم می‌گیرن، نه عرض واقعی والد خودشون. حتی اگه عرض کل صفحه ۱۲۰۰ پیکسل باشه، کارتی که داخل یک Sidebar ۲۵۰ پیکسلی قرار داره، همچنان می‌خواد به‌صورت افقی (row) چیده بشه؛ که این، برای اون فضای باریک، اصلاً مناسب نیست.

راه‌حل: Container Queries

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

.card {
  display: flex;
  flex-direction: column;
}

@container (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}

Container Queries، اجازه می‌دن، به‌جای واکنش به عرض کل Viewport، به عرض یک عنصر والد مشخص واکنش نشون بدیم. حالا، این کارت، صرف‌نظر از عرض کل صفحه، فقط بر اساس عرض همون Containerای که واقعاً داخلشه، تصمیم می‌گیره چطور چیده بشه.

گام اول: تعریف یک Containment Context با container-type

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

پیش از این‌که بتونیم از @container استفاده کنیم، باید مشخص کنیم کدوم عنصر، قراره به‌عنوان «مرجع اندازه‌گیری» عمل کنه. Property به اسم container-type، این نقش رو مشخص می‌کنه.

مقدار رفتار
inline-size فقط عرض (در محور Inline که در فصل ۱۱ دیدیم) به‌عنوان مرجع اندازه‌گیری در نظر گرفته می‌شود
size هم عرض و هم ارتفاع، به‌عنوان مرجع اندازه‌گیری در نظر گرفته می‌شوند

در اکثر کاربردهای عملی، inline-size کافیه؛ چون معمولاً عرض، عامل تعیین‌کننده‌تری برای چیدمان نسبت به ارتفاعه.

گام دوم (اختیاری): نام‌گذاری Container

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

@container card (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}

Property به اسم container-name، به یک Container، اسم دلخواه می‌ده؛ این کار، مخصوصاً وقتی چند Container تودرتو داریم و می‌خوایم مشخص کنیم @container دقیقاً به کدوم‌شون اشاره داره، مفیده. اگه اسمی تعریف نکنیم، @container به‌طور خودکار، نزدیک‌ترین Container والد رو در نظر می‌گیره.

شورت‌هند container

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

دقیقاً مثل خیلی از Propertyهایی که در این دوره دیدیم، container، شورت‌هندی برای container-name و container-type با هم است.

یک مثال کامل و مقایسه‌ای

.sidebar-wrapper,
.main-wrapper {
  container-type: inline-size;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

@container (min-width: 400px) {
  .card {
    flex-direction: row;
    align-items: center;
  }
}
<div class="main-wrapper" style="width: 800px;">
  <div class="card">کارت در بخش اصلی (افقی می‌شود)</div>
</div>

<div class="sidebar-wrapper" style="width: 250px;">
  <div class="card">همین کارت، در سایدبار (ستونی می‌ماند)</div>
</div>

حالا، با همون یک تعریف @container، هرکدوم از این دو کارت، مستقل از هم، بر اساس عرض واقعی والد خودشون، تصمیم می‌گیرن؛ کارت داخل .main-wrapper (که ۸۰۰ پیکسله)، افقی می‌شه، ولی کارت داخل .sidebar-wrapper (که فقط ۲۵۰ پیکسله)، ستونی باقی می‌مونه.

واحدهای Container Query: cqw، cqh و مشابه‌ها

.card-title {
  font-size: 5cqw;
}

دقیقاً مشابه واحدهای Viewport (vw، vh) که در فصل ۶ دیدیم، Container Queries هم خانواده واحد اختصاصی خودشون رو دارن؛ این واحدها، به‌جای Viewport، نسبت به اندازه همون Container محاسبه می‌شن.

واحد معادل با
cqw 1٪ از عرض Container (معادل مفهومی vw)
cqh 1٪ از ارتفاع Container (معادل مفهومی vh)
cqi 1٪ از اندازه Container در محور Inline
cqb 1٪ از اندازه Container در محور Block

در مثال بالا، 5cqw، یعنی «۵٪ از عرض Container»؛ یعنی این تیتر، بدون هیچ @containerای، مستقیماً و به‌طور پیوسته، با عرض همون Container (نه Viewport)، رشد یا کوچک می‌شه؛ دقیقاً همون فلسفه Fluid Typography که در درس‌های قبل دیدیم، ولی این‌بار نسبت به والد، نه کل صفحه.

Media Query در برابر Container Query: چه زمانی کدام؟

معیار Media Query Container Query
مرجع اندازه‌گیری کل Viewport یک عنصر والد مشخص
مناسب برای چیدمان کلی صفحه (Layout سطح بالا) کامپوننت‌های قابل‌استفاده مجدد در جاهای مختلف
وضعیت پشتیبانی گسترده و دیرینه جدیدتر؛ نیاز به بررسی در پروژه‌های با نیاز سازگاری بالا

نکات مهم

  • Container Queries، امکان واکنش‌گرایی بر اساس عرض یک عنصر والد مشخص را فراهم می‌کنند، نه کل Viewport.
  • پیش از استفاده از @container، باید با container-type (و اختیاراً container-name) روی والد، یک Containment Context تعریف شود.
  • واحدهای cqw، cqh، cqi و cqb، معادل مفهومی واحدهای Viewport هستند، ولی نسبت به اندازه Container محاسبه می‌شوند.

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

  • استفاده از @container بدون تعریف container-type روی یک والد مناسب، که باعث می‌شود این قانون هیچ اثری نداشته باشد.
  • استفاده از Container Query در جایی که هدف، واکنش به کل صفحه است، نه یک کامپوننت مشخص؛ که در این حالت، Media Query انتخاب مناسب‌تری است.
  • عدم بررسی وضعیت پشتیبانی مرورگر پیش از استفاده گسترده در پروژه‌هایی با نیاز به سازگاری با مرورگرهای قدیمی‌تر.

جمع‌بندی

Container Queries، با قانون @container و Property به اسم container-type، امکان طراحی کامپوننت‌هایی را فراهم می‌کنند که بر اساس عرض والد واقعی خودشان (نه کل Viewport) واکنش نشان می‌دهند؛ این ویژگی، برای کامپوننت‌های قابل‌استفاده مجدد در جاهای مختلف صفحه (مثل یک کارت که هم در بخش اصلی و هم در سایدبار استفاده می‌شود)، ابزاری ضروری است. واحدهای cqw و مشابه‌ها هم، همین منطق را برای اندازه‌گیری پیوسته (مثل Fluid Typography) نسبت به Container، به‌جای Viewport، فراهم می‌کنند.