تاروپود — خودآموز HTML و CSS

فصل ۵ از ۸

پیشرفت ترم
۰٪

ترم ۴ · Responsive

container query

فصل ۵پیش‌نمایش رایگان
۹ دقیقه مطالعه فصل ۵

در این فصل چه می‌سازی#

تا اینجا با @media یاد گرفتی به عرضِ کلِ صفحه واکنش نشان بدهی. ولی یک کارت که هم در یک ستونِ پهن و هم در یک نوارِ باریکِ کناری به‌کار می‌رود، نباید به عرضِ صفحه نگاه کند — باید به عرضِ جای خودش نگاه کند. در این فصل ابزارِ دقیقاً همین کار را یاد می‌گیری: container query. آخر فصل یک کارت داری که در جای پهن، دو ستونی و افقی می‌شود، و همان کارت در جای باریک، ساده و عمودی می‌ماند — بی‌آنکه یک خط از خودِ کارت را عوض کنی.

دو کارتِ یک‌شکل روی صفحه: کارتِ سمتِ راست در یک ناحیهٔ پهن، افقی و دوقسمتی چیده شده و یک فلش به لبهٔ همان ناحیه اشاره دارد؛ کارتِ سمتِ چپ در یک ستونِ باریک، عمودی مانده و فلشش به لبهٔ ستونِ باریک اشاره دارد

آخر این فصل می‌توانی:

  • یک عنصر را با container-type به یک container تبدیل کنی
  • با @container بنویسی که فرزندها به عرضِ همان container واکنش نشان بدهند
  • در یک جمله بگویی @container با @media چه فرقِ بنیادی‌ای دارد
  • بگویی چرا AI اینجا با اعتمادبه‌نفس اشتباه می‌گوید و خودت ردش کنی

قبل از شروع#

  • از فصل ۲ (همین ترم): @media و min-width@container خواهرِ نزدیکِ آن است، با یک تفاوتِ مهم.
  • از ترم ۳: display: flex و grid — کارت را با flex به دو قسمت می‌کنیم.
  • از ترم ۲ فصل ۹: custom property و فکرِ «یک component، چند جا».

مثل همیشه فقط یک مرورگر لازم داری، یا فقط همین آزمایشگاه.

۱. مشکلی که @media حلش نمی‌کند#

یک کارت را در نظر بگیر که یک <h2> و یک <p> دارد. همین کارت را دو جا گذاشته‌ای: یک‌بار در ناحیهٔ پهنِ اصلی، یک‌بار در یک نوارِ کناریِ باریک.

<main class="areas">
  <section class="wide">
    <article class="card">
      <h2>مطلبِ اصلی</h2>
      <p>این کارت به عرضِ جای خودش نگاه می‌کند، نه به عرضِ کلِ صفحه.</p>
    </article>
  </section>

  <aside class="narrow">
    <article class="card">
      <h2>یک نکته</h2>
      <p>همین کارت اینجا در یک ستونِ باریک نشسته، پس ساده و عمودی می‌ماند.</p>
    </article>
  </aside>
</main>

می‌خواهی کارتِ داخلِ ناحیهٔ پهن، افقی و دوقسمتی شود (تیتر یک طرف، متن طرفِ دیگر)، ولی کارتِ داخلِ نوارِ باریک همان‌طور عمودی بماند. حالا فکر کن با @media می‌شود؟ نه. @media فقط یک چیز را می‌داند: عرضِ کلِ پنجره. هر دو کارت روی یک پنجره‌اند، پس @media نمی‌تواند یکی را افقی و دیگری را عمودی کند — هر تصمیمی بگیرد، برای هر دو می‌گیرد. مشکل این است که کارتِ باریک، در یک پنجرهٔ پهن هم باید باریک بماند. @media عرضِ پنجره را می‌بیند؛ ما به عرضِ جای کارت نیاز داریم.

۲. @container: به عرضِ جای خودت نگاه کن#

اینجاست که container query وارد می‌شود. به‌جای «وقتی پنجره پهن شد»، می‌گویی «وقتی جای این کارت پهن شد». قاعده‌اش خیلی شبیهِ @media است، فقط به‌جای @media می‌نویسی @container:

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

  .card h2 {
    flex: 0 0 40%;
    display: flex;
    align-items: center;
  }
}

این یعنی «هر جا عرضِ container به ۴۰۰ پیکسل رسید، کارتِ داخلش را افقی کن». این را در آزمایشگاه اضافه کن و صفحه را نگاه کن.

هیچ اتفاقی نمی‌افتد. هر دو کارت هنوز عمودی‌اند — حتی آن‌که در ناحیهٔ پهن است. انگار کلِ این قاعده نادیده گرفته شد.

🔧 اگر کار نکرد: درست است، و باید همین را ببینی. @container می‌پرسد «عرضِ container چقدر است؟» — ولی ما هنوز به هیچ عنصری نگفته‌ایم که «تو یک container هستی». بدونِ آن، این پرسش هیچ عنصری برای اندازه‌گرفتن ندارد، پس قاعده هرگز اجرا نمی‌شود. باید والدِ کارت را صریح یک container اعلام کنیم. این کارِ property بعدی است: container-type.

۳. container-type: والد را container کن#

روی عنصری که کارت داخلش است — یعنی .wide و .narrow — یک خط اضافه کن که می‌گوید «عرضِ من را قابلِ پرسش کن»:

.wide,
.narrow {
  container-type: inline-size;
}

inline-size یعنی «عرض را رصد کن» (در فارسیِ راست‌به‌چپ هم منظور همان بُعدِ افقیِ متن است). حالا هر کدام از این دو ناحیه یک container است، و @container بالاخره چیزی برای اندازه‌گرفتن دارد.

چک کن: حالا کارتِ ناحیهٔ پهن افقی شد — تیترِ آبی یک طرف، متن طرفِ دیگر — ولی کارتِ نوارِ باریک هنوز عمودی است. دو کارتِ کاملاً هم‌قاعده، با دو شکلِ متفاوت، چون هر کدام عرضِ container خودش را دارد: ناحیهٔ پهن از ۴۰۰ رد شده، نوارِ باریک نه. حالا عرضِ آزمایشگاه را کم و زیاد کن: کارتِ باریک هیچ‌وقت تغییر نمی‌کند (چون container اش همیشه باریک است)، ولی کارتِ پهن وقتی جایش زیرِ ۴۰۰ برود، خودش عمودی می‌شود.

⚠️ مواظب باش: container-type را روی والد بگذار، نه روی خودِ کارت. یک container عرضِ فرزندهایش را کنترل می‌کند؛ اگر روی خودِ .card بگذاری‌اش، کارت نمی‌تواند عرضِ خودش را از خودش بپرسد، و باز هیچ‌چیز کار نمی‌کند.

۴. تفاوتِ بنیادی با @media#

حالا مهم‌ترین جملهٔ این فصل: @media عرضِ پنجره را می‌بیند؛ @container عرضِ نزدیک‌ترین container را می‌بیند. @media یک نگاهِ سراسری است — برای کلِ صفحه یک تصمیم می‌گیرد. @container یک نگاهِ محلی است — به هر component اجازه می‌دهد به جای خودش واکنش نشان بدهد. برای همین یک کارت که با @container نوشته شده، هر جای صفحه بگذاری‌اش درست کار می‌کند: اسکلتِ صفحه دیگر مهم نیست، خودِ کارت می‌داند چه‌کار کند.

قبل از ادامه، جواب بده: اگر همین کارت را از نوارِ باریک برداری و در ناحیهٔ پهن بگذاری، شکلش عوض می‌شود؟

بله. کارت به هیچ کلاسِ تازه‌ای وابسته نیست؛ فقط عرضِ container جدیدش را می‌خواند. همین نقلِ‌مکان کافی است تا افقی شود. این کاری است که @media هرگز نمی‌توانست بکند.

۵. کد کاملِ این فصل#

اول index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>container query</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="areas">
      <section class="wide">
        <article class="card">
          <h2>مطلبِ اصلی</h2>
          <p>این کارت به عرضِ جای خودش نگاه می‌کند، نه به عرضِ کلِ صفحه.</p>
        </article>
      </section>

      <aside class="narrow">
        <article class="card">
          <h2>یک نکته</h2>
          <p>همین کارت اینجا در یک ستونِ باریک نشسته، پس ساده و عمودی می‌ماند.</p>
        </article>
      </aside>
    </main>
  </body>
</html>

و styles.css:

/* styles.css */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 20px;
  font-family: Tahoma, sans-serif;
  background-color: #f6edda;
  color: #2c2114;
}

.areas {
  display: grid;
  grid-template-columns: 1fr 240px;
  gap: 20px;
}

/* har naahiye ra yek container elam mikonim */
.wide,
.narrow {
  container-type: inline-size;
}

.card {
  background-color: #ffffff;
  border: 1px solid #d8ccae;
  border-radius: 12px;
  overflow: hidden;
}

.card h2 {
  margin: 0;
  padding: 16px;
  background-color: #2a6b93;
  color: #ffffff;
}

.card p {
  margin: 0;
  padding: 16px;
  line-height: 1.9;
}

/* vaghti container-e kart pahntar az 400px shod, radifi sho */
@container (min-width: 400px) {
  .card {
    display: flex;
  }

  .card h2 {
    flex: 0 0 40%;
    display: flex;
    align-items: center;
  }
}

دو جعبهٔ تودرتو: جعبهٔ بیرونی که «container» است و روی لبه‌اش یک نشانه دارد، و یک کارتِ فرزند داخلش که فلشی از فرزند به لبهٔ جعبهٔ بیرونی می‌رود و نشان می‌دهد فرزند عرضِ والد را می‌خواند

۶. همیار AI: کمکِ درست، اشتباهِ رایج#

container query تازه است و دقیقاً همان‌جایی است که AI زیاد اشتباه می‌کند — چون سال‌ها فقط @media وجود داشت و خیلی از جواب‌هایی که AI یاد گرفته، از آن دورانند. سه قانونِ ثابتِ کار با AI در کلِ این خودآموز اینهاست:

🤖 با AI: سه قانون را عیناً به خاطر بسپار: «اول خودت، بعد AI» — اول با معیارهای خودت تصمیم بگیر، بعد نظر AI را به‌عنوان نظرِ دوم بشنو، نه جوابِ نهایی. «خط به خط» — هر چیزی که AI می‌دهد را خط‌به‌خط بخوان و بفهم، هیچ‌وقت یک‌جا کپی نکن. «AI هم اشتباه می‌کند» — و همین الان یک نمونه‌اش را با هم می‌بینیم.

کاربردِ خوب. بعد از اینکه خودت container query را نوشتی، AI یک جفت‌چشمِ دومِ خوب است:

🤖 با AI: کدِ خودت را به دستیار AI ات بده و بپرس: «container-type را روی درست‌ترین عنصر گذاشته‌ام؟ و آیا 400px نقطهٔ خوبی برای شکستِ این کارت است؟». جوابش را با چیزی که در آزمایشگاه می‌بینی بسنج: عددِ پیشنهادی‌اش را امتحان کن و ببین کارت واقعاً همان‌جا که باید، تمیز می‌شکند یا نه.

کاربردِ اشتباه. حالا نمونه‌ای که AI با اعتمادبه‌نفسِ کامل اشتباه می‌گوید. فرض کن می‌پرسی «چطور کاری کنم این کارت به عرضِ جای خودش واکنش نشان بدهد؟». خیلی وقت‌ها جوابی شبیه این می‌دهد:

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

«فقط یک @media بگذار، وقتی صفحه از ۴۰۰ رد شد کارت افقی می‌شود.» جمله‌اش قاطع و منطقی به‌نظر می‌رسد. ولی در آزمایشگاه امتحانش کن: این @media را بگذار و پنجره را پهن کن. هر دو کارت با هم افقی می‌شوند — حتی کارتِ نوارِ باریک، که در جای تنگش زشت و درهم می‌شود. AI به سؤالِ «جای خودش» جوابِ «عرضِ پنجره» داد.

چرا؟ با همان چیزی که در این فصل فهمیدی می‌توانی ردش کنی: @media عرضِ پنجره را می‌بیند، نه عرضِ container را. جوابِ درست همان است که خودت نوشتی — @container روی والدی که container-type دارد. این بهترین درسِ «AI هم اشتباه می‌کند» است: AI جمله‌ای ساخت که درست به‌نظر می‌رسید ولی به سؤالِ دیگری جواب می‌داد، و تو با فهمِ خودت از تفاوتِ @container و @media توانستی ردش کنی. اول خودت، بعد AI.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
container query کانتینِر کوئری قاعده‌ای که فرزند به عرضِ container خودش واکنش نشان می‌دهد، نه به عرضِ پنجره
container-type کانتینِر تایپ والد را یک container اعلام می‌کند تا عرضش قابلِ پرسش شود
inline-size این‌لاین سایز مقداری که می‌گوید فقط بُعدِ افقی (عرض) رصد شود
@container اَت کانتینِر قاعدهٔ شرطی که وقتی عرضِ container به حدی رسید اجرا می‌شود

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

الان می‌توانی هر component را وادار کنی به جای خودش واکنش نشان بدهد. فصل بعد سراغِ یک تکهٔ واقعیِ هر سایت می‌رویم که در موبایل دردسر می‌شود: منوی ناوبری. یاد می‌گیری یک منوی موبایلِ بازشو بسازی که با صفحه‌کلید هم کار کند و یک خط JavaScript هم نخواهد — فقط با <details> و <summary>.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.