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

فصل ۱۱ از ۱۱

پیشرفت ترم
۰٪

ترم ۳ · Layout: چیدمان

پروژه: layout یک سایت کامل

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

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

این آخرین فصلِ ترمِ Layout است و مفهومِ تازه‌ای ندارد. کارِ امروز ترکیب است: هر ابزاری که این ترم یاد گرفتی — flexbox، grid، position و logical properties — را کنارِ هم می‌گذاریم و layout کاملِ یک سایتِ واقعی را از صفر می‌سازیم. یک صفحهٔ گالریِ هنری به‌نامِ «هنرکده»: یک سرصفحه که لوگو و منویش با flexbox دو سرِ خط نشسته‌اند، یک بدنهٔ گرید که محتوای اصلی و یک ستونِ کناری را کنارِ هم می‌چیند، یک ستونِ کناری که موقعِ پیمایش می‌چسبد، و یک پاصفحه. آخرِ این فصل یک صفحه داری که واقعاً مثلِ یک سایت چیده شده.

طرحِ سیمیِ یک صفحهٔ کامل: سرصفحه با لوگو یک سمت و منو سمتِ دیگر، زیرش یک بدنهٔ دوستونی با ستونِ پهنِ محتوا و ستونِ باریکِ کناری، و یک پاصفحه در پایین

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

  • یک سرصفحه بسازی که لوگو و منویش با flexbox دو سرِ خط بنشینند
  • بدنهٔ صفحه را با grid به دو ستونِ محتوا و کناری تقسیم کنی
  • یک ستونِ کناری را با position: sticky موقعِ پیمایش بچسبانی
  • یک صفحهٔ کامل را با ابزارهای این ترم، امن برای فارسی، سرِ هم کنی

قبل از شروع#

  • از فصل‌های ۲ تا ۴: flexboxdisplay: flex، justify-content، align-items، gap.
  • از فصل‌های ۵ تا ۷: griddisplay: grid، grid-template-columns، واحدِ fr، gap.
  • از فصل ۸: position، به‌خصوص sticky.
  • از فصل ۱۰: logical properties برای فاصله و جای امنِ فارسی.

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

۱. اسکلت و پایه#

اول به ساختارِ صفحه نگاه کن. semantic است، همان‌طور که در ترم ۱ یاد گرفتی: یک <header> برای سرصفحه، یک <main> برای بدنه که داخلش یک <div class="content"> و یک <aside> دارد، و یک <footer>. کلاس‌ها فقط برای اینکه با CSS راحت هدفشان بگیریم اضافه شده‌اند:

<header class="site-header">
  <a class="logo" href="#">هنرکده</a>
  <nav>
    <ul class="menu">
      <li><a href="#works">کارها</a></li>
      <li><a href="#about">درباره</a></li>
      <li><a href="#contact">تماس</a></li>
    </ul>
  </nav>
</header>

<main class="layout">
  <section class="content"><!-- kart-haye mohteva inja --></section>
  <aside class="sidebar"><!-- sotoon-e kenari inja --></aside>
</main>

<footer class="site-footer"><!-- pasafhe inja --></footer>

حالا پایهٔ CSS را می‌گذاریم: box-sizing سراسری از فصل ۴، یک theme کوچک با custom property از فصل ۹ ترم ۲، و کمی جانِ اولیه به body:

* {
  box-sizing: border-box;
}

:root {
  --brand: #2a6b93;
  --ink: #2c2114;
  --paper: #f6edda;
  --card: #ffffff;
  --line: #d8ccae;
  --space: 20px;
}

body {
  margin: 0;
  font-family: Tahoma, sans-serif;
  color: var(--ink);
  background-color: var(--paper);
  line-height: 1.8;
}

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

۲. سرصفحه با flexbox#

سرصفحه دو چیز دارد که باید دو سرِ خط بنشینند: لوگو یک طرف، منو طرفِ دیگر. این دقیقاً کاری است که flexbox با justify-content: space-between می‌کند — از فصل ۳. خودِ منو هم یک ردیفِ افقی از لینک‌هاست، پس آن را هم flex می‌کنیم:

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space);
  padding-block: 14px;
  padding-inline: 24px;
  background-color: var(--card);
  border-block-end: 1px solid var(--line);
}

.logo {
  font-weight: 700;
  font-size: 1.4rem;
  color: var(--brand);
}

.menu {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 0;
}

.menu a {
  color: var(--ink);
  font-weight: 700;
}

چک کن: لوگوی «هنرکده» یک سرِ خط نشست و سه لینکِ منو سرِ دیگر، در یک ردیفِ افقی و با فاصلهٔ منظم. space-between فضای خالی را انداخت وسط، پس دو گروه به دو لبه چسبیدند. دقت کن که padding-inline و border-block-end را logical نوشتیم — پس سرصفحه در صفحهٔ راست‌به‌چپ درست است و اگر روزی چپ‌به‌راست شود هم می‌ماند.

💡 نکته: نقطه‌های فهرست کجا رفتند؟ وقتی <ul> را display: flex کردیم، هر <li> تبدیل به یک flex item شد و — درست مثلِ آن inline-blockِ ترم ۲ — دیگر «آیتمِ فهرست» حساب نمی‌شود، پس نقطه‌ها خودبه‌خود ناپدید شدند. منو یک <ul>ِ واقعی می‌ماند — که برای screen reader درست است، «فهرستی از لینک‌ها» — ولی ظاهرش یک نوارِ افقی شد.

۳. بدنه با grid#

حالا مهم‌ترین بخش: بدنه باید دو ستون داشته باشد — یک ستونِ پهن برای محتوا و یک ستونِ باریک‌ترِ ثابت برای کناری. این کارِ grid است. با grid-template-columns: 1fr 260px می‌گوییم ستونِ اول هرچه فضا ماند را بگیرد (1fr) و ستونِ دوم دقیقاً 260px باشد:

.layout {
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: var(--space);
  max-width: 960px;
  margin-inline: auto;
  padding: var(--space);
}

.content {
  display: grid;
  gap: var(--space);
}

.card {
  padding: var(--space);
  background-color: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.card h2 {
  margin-block: 0 10px;
}

چک کن: بدنه دو ستون شد — کارت‌های محتوا سمتِ راست (چون صفحه rtl است و ستونِ اولِ گرید از راست شروع می‌شود) و ستونِ کناری سمتِ چپ. کلِ بدنه هم با margin-inline: auto وسطِ صفحه جمع شد و از 960px پهن‌تر نمی‌شود. خودِ .content را هم یک گرید کردیم تا کارت‌هایش با gap فاصلهٔ منظم بگیرند.

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

⚠️ مواظب باش: این layout هنوز responsive نیست. اگر پنجره را خیلی باریک کنی، آن 260px جا نمی‌شود و صفحه به‌هم می‌ریزد. اینکه در صفحهٔ موبایل این دو ستون بروند زیرِ هم، کارِ @media است که ابزارش را نداریم. این را در ترم ۴ کامل یاد می‌گیریم. فعلاً صفحه را در عرضِ معمولی نگه دار.

۴. ستونِ کناری که می‌چسبد#

می‌خواهیم وقتی کاربر صفحه را پایین می‌برد، ستونِ کناری همراهش نرود و بالای صفحه بچسبد. از فصل ۸ می‌دانی این کارِ position: sticky است. اول همین را امتحان کن:

.sidebar {
  position: sticky;
}

صفحه را پایین ببر. ستونِ کناری... هیچ کاری نمی‌کند. با محتوا بالا می‌رود، انگار sticky ای در کار نیست.

🔧 اگر کار نکرد: sticky گذاشتی و نچسبید؟ این رایج‌ترین اشتباهِ sticky است: position: sticky به‌تنهایی کافی نیست — باید بگویی کجا بچسبد. بدونِ یک فاصله‌ٔ مرجع، مرورگر نمی‌داند در چه نقطه‌ای باید عنصر را نگه دارد، پس مثلِ یک عنصرِ عادی رفتار می‌کند. باید یک offset بدهی؛ مثلاً inset-block-start: 16px یعنی «وقتی به ۱۶ پیکسلیِ بالای صفحه رسیدی، همان‌جا بچسب». (این همان top: 16px است، ولی logical و امن برای هر جهت.)

یک نکتهٔ دوم هم هست: عنصرِ گرید به‌طور پیش‌فرض به بلندیِ کلِ ردیف کش می‌آید، و چیزی که کلِ ارتفاع را پُر کرده جایی برای «چسبیدن» ندارد. با align-self: start — همان align-selfِ فصل ۳، که در گرید هم کار می‌کند — می‌گوییم ستونِ کناری فقط به‌اندازهٔ محتوایش بلند باشد تا فضای چسبیدن پیدا کند:

.sidebar {
  align-self: start;
  position: sticky;
  inset-block-start: 16px;
  padding: var(--space);
  background-color: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.sidebar h2 {
  margin-block: 0 10px;
}

چک کن: حالا صفحه را پایین ببر. ستونِ محتوا بالا می‌رود، ولی جعبهٔ «دسته‌ها» در فاصلهٔ ۱۶ پیکسلیِ بالای صفحه می‌چسبد و همان‌جا می‌ماند تا وقتی به پاصفحه برسی. آن دو خط — align-self: start و inset-block-start — کاری کردند که sticky واقعاً کار کند.

۵. پاصفحه و صفحهٔ کامل#

آخرین تکه، پاصفحه است: یک خطِ ساده و وسط‌چین با یک مرزِ بالایی که جدایش می‌کند:

.site-footer {
  padding-block: 20px;
  text-align: center;
  color: var(--ink);
  border-block-start: 1px solid var(--line);
}

و حالا هر دو فایلِ کامل. اول index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8">
    <title>هنرکده</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <header class="site-header">
      <a class="logo" href="#">هنرکده</a>
      <nav>
        <ul class="menu">
          <li><a href="#works">کارها</a></li>
          <li><a href="#about">درباره</a></li>
          <li><a href="#contact">تماس</a></li>
        </ul>
      </nav>
    </header>

    <main class="layout">
      <section class="content">
        <article id="works" class="card">
          <h2>کارها</h2>
          <p>اینجا مجموعه‌ای از کارها را نشان می‌دهیم. هر کار یک عکس و یک توضیح کوتاه دارد.</p>
          <p>وقتی صفحه را پایین می‌بری، این ستون بلند حرکت می‌کند ولی ستونِ کناری سرِ جایش می‌ماند.</p>
        </article>

        <article id="about" class="card">
          <h2>درباره</h2>
          <p>هنرکده یک نمونهٔ تمرینی است تا layout یک صفحهٔ واقعی را کنارِ هم بچینیم.</p>
          <p>سرصفحه با flexbox، بدنه با grid، و ستونِ کناری با position چیده شده‌اند.</p>
        </article>

        <article id="contact" class="card">
          <h2>تماس</h2>
          <p>این هم آخرین بخش، تا ستون به‌اندازهٔ کافی بلند شود و بشود صفحه را پایین برد.</p>
          <p>حالا به ستونِ کناری نگاه کن که موقعِ پیمایش چطور می‌چسبد.</p>
        </article>
      </section>

      <aside class="sidebar">
        <h2>دسته‌ها</h2>
        <ul>
          <li><a href="#">نقاشی</a></li>
          <li><a href="#">عکاسی</a></li>
          <li><a href="#">خط</a></li>
        </ul>
      </aside>
    </main>

    <footer class="site-footer">
      <p>ساختهٔ من — ۱۴۰۵</p>
    </footer>
  </body>
</html>

و styles.css، از اول تا آخر:

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

:root {
  --brand: #2a6b93;
  --ink: #2c2114;
  --paper: #f6edda;
  --card: #ffffff;
  --line: #d8ccae;
  --space: 20px;
}

body {
  margin: 0;
  font-family: Tahoma, sans-serif;
  color: var(--ink);
  background-color: var(--paper);
  line-height: 1.8;
}

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space);
  padding-block: 14px;
  padding-inline: 24px;
  background-color: var(--card);
  border-block-end: 1px solid var(--line);
}

.logo {
  font-weight: 700;
  font-size: 1.4rem;
  color: var(--brand);
}

.menu {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 0;
}

.menu a {
  color: var(--ink);
  font-weight: 700;
}

.layout {
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: var(--space);
  max-width: 960px;
  margin-inline: auto;
  padding: var(--space);
}

.content {
  display: grid;
  gap: var(--space);
}

.card {
  padding: var(--space);
  background-color: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.card h2 {
  margin-block: 0 10px;
}

.sidebar {
  align-self: start;
  position: sticky;
  inset-block-start: 16px;
  padding: var(--space);
  background-color: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
}

.sidebar h2 {
  margin-block: 0 10px;
}

.site-footer {
  padding-block: 20px;
  text-align: center;
  color: var(--ink);
  border-block-start: 1px solid var(--line);
}

چک کن: صفحهٔ کامل را نگاه کن — سرصفحهٔ flex با لوگو و منوی دو سر، بدنهٔ گرید با دو ستون، ستونِ کناری که موقعِ پیمایش می‌چسبد، و پاصفحهٔ وسط‌چین. هر چیزی که این ترم یاد گرفتی در همین یک صفحه هست: flexbox برای سرصفحه، grid برای بدنه، position: sticky برای کناری، z-index و context ها زیرِ پوستِ لایه‌ها، و logical properties که همه را برای فارسی امن کرد.

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

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

این فصل یک فصلِ پروژه‌ای است و واژهٔ فنیِ تازه‌ای ندارد؛ فقط ابزارهای این ترم — flexbox، grid، position: sticky و logical properties — را کنارِ هم به‌کار می‌گیرد.

تمرین‌ها

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

در فصل بعد#

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

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

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