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

فصل ۹ از ۹

پیشرفت ترم
۰٪

ترم ۷ · پل به JavaScript

پروژهٔ نهایی: یک صفحهٔ قدیمی را تعاملی کن

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

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

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

صفحهٔ نمونه‌کار که نیمی روشن و نیمی تیره است، با یک کلید تغییر حالت، فهرستی در حال فیلترشدن از میان یک قیف، و فرمی با علامت تأیید سبز

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

  • با classList.toggle و custom property ها تمِ کلِ صفحه را عوض کنی
  • یک فهرست را از روی چند آرایه، بسته به انتخابِ کاربر، از نو بسازی
  • به فرم بازخوردِ خطا و موفقیت بدهی
  • پروژه را با یک چک‌لیستِ تحویل، مثل یک حرفه‌ای ببندی

قبل از شروع#

  • از فصل ۵: classList.toggle — و از ترم ۲: custom property ها.
  • از فصل ۷: آرایه، for...of، createElement/append و الگوی «اول خالی کن، بعد از نو بساز».
  • از فصل ۶: value، submit، preventDefault و if/else.

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

۱. صفحه‌ای که تحویل می‌گیری#

صفحه یک نمونه‌کارِ کوچک است: سرصفحه‌ای با اسم و دکمهٔ «تغییر تم» که هنوز کاری نمی‌کند، بخشِ پروژه‌ها با سه دکمهٔ فیلتر، فرمِ تماس، و پاصفحه:

<!-- 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>نمونه‌کارهای پروانه</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <header class="site-header">
      <h1>نمونه‌کارهای پروانه</h1>
      <button id="theme-btn" type="button">تغییر تم</button>
    </header>

    <main>
      <section class="projects">
        <h2>پروژه‌های من</h2>
        <button id="all-btn" class="filter" type="button">همه</button>
        <button id="web-btn" class="filter" type="button">سایت‌ها</button>
        <button id="fun-btn" class="filter" type="button">تمرین‌های سرگرمی</button>
        <ul id="project-list">
          <li>کارت پروفایل</li>
          <li>وبلاگ شخصی</li>
          <li>صفحهٔ فرود محصول</li>
          <li>گالری عکس</li>
          <li>کارت تبریک</li>
        </ul>
      </section>

      <section class="contact">
        <h2>پیام بده</h2>
        <form id="contact-form">
          <label for="name-input">اسمت:</label>
          <input type="text" id="name-input">
          <button type="submit">بفرست</button>
        </form>
        <p id="form-note"></p>
      </section>
    </main>

    <footer class="site-footer">
      <p>ساختهٔ پروانه — پایانِ تاروپود</p>
    </footer>

    <script src="script.js"></script>
  </body>
</html>

به یک تصمیم دقت کن: فهرستِ پروژه‌ها را عمداً در HTML هم نوشته‌ایم تا صفحه حتی بدونِ JavaScript هم کامل دیده شود — همان درسِ فصلِ قبل: JS تزئین است، نه ستون.

CSS صفحه با custom property ها نوشته شده و یک قاعدهٔ آماده هم منتظرِ توست:

body.dark {
  --bg: #201a12;
  --surface: #2c2114;
  --ink: #f6edda;
  --accent: #7fb4d6;
}

یعنی هر وقت body یک class به اسمِ dark بگیرد، همهٔ رنگ‌های صفحه با هم عوض می‌شوند. CSS اش هست؛ فقط مانده کسی آن class را روشن و خاموش کند — کارِ تو.

۲. کلیدِ تم#

با دانشِ فصل ۵، اولین تلاشِ خیلی‌ها این است:

const themeBtn = document.querySelector('#theme-btn');

themeBtn.addEventListener('click', function () {
  themeBtn.classList.toggle('dark');
});

دکمه را بزن. هیچ اتفاقی نمی‌افتد. نه خطایی در console، نه تغییری روی صفحه — از همان خرابی‌های بی‌صدا که از ترم‌های CSS خوب می‌شناسی.

🔧 اگر کار نکرد: در DevTools به عنصرها نگاه کن: class dark دارد اضافه و کم می‌شود، ولی روی دکمه. selector تو در CSS body.dark است — قاعده منتظرِ dark روی body است، نه روی دکمه. class را همان‌جایی toggle کن که CSS منتظرش است — و document.body میان‌برِ همیشگیِ JS به خودِ <body> است، بدونِ نیاز به querySelector:

themeBtn.addEventListener('click', function () {
  document.body.classList.toggle('dark');
});

چک کن: با هر کلیک، کلِ صفحه بینِ روشن و تیره می‌رود — و آن transition ی که از ترم ۵ می‌شناسی، جابه‌جایی را نرم کرده است.

۳. فیلترِ پروژه‌ها#

داده را مثل فصل ۷ نگه می‌داریم: هر دسته یک آرایه، و «همه» را هم با همان push و حلقه می‌سازیم:

const webProjects = ['کارت پروفایل', 'وبلاگ شخصی', 'صفحهٔ فرود محصول'];
const funProjects = ['گالری عکس', 'کارت تبریک'];

const allProjects = [];
for (const project of webProjects) {
  allProjects.push(project);
}
for (const project of funProjects) {
  allProjects.push(project);
}

بعد سه دکمه، سه listener — هرکدام همان الگوی آشنا: اول خالی کن، بعد بساز:

webBtn.addEventListener('click', function () {
  projectList.textContent = '';
  for (const project of webProjects) {
    const item = document.createElement('li');
    item.textContent = project;
    projectList.append(item);
  }
});

دو listener دیگر همین‌اند، فقط با آرایهٔ خودشان (کدِ کامل پایین است). بله — سه تکهٔ تقریباً یکسان نوشتیم و این تکرار آزاردهنده است. قرار هم هست باشد: کوتاه‌کردنِ این تکرار یعنی تعریفِ functionِ خودت، و آن دقیقاً اولین درسِ مسیرِ JavaScript بعد از این پل است. امروز، تکرارِ صادقانه بهتر از ابزاری است که هنوز یاد نگرفته‌ای.

چک کن: «سایت‌ها» سه پروژه نشان می‌دهد، «تمرین‌های سرگرمی» دو تا، و «همه» هر پنج تا — بدونِ هیچ دوقلوشدنی، چون قبل از هر ساختن خالی می‌کنی.

۴. بازخوردِ فرم#

فرمِ بی‌جواب، دیوارِ بی‌در است. با ابزارِ فصل ۶ (سه عنصرِ فرم را هم با querySelector گرفته‌ایم — کدِ کامل پایین):

form.addEventListener('submit', function (event) {
  event.preventDefault();

  if (nameInput.value === '') {
    formNote.textContent = 'اسمت را ننوشته‌ای — فرمِ خالی جایی نمی‌رود.';
    formNote.classList.add('error');
  } else {
    formNote.textContent = `ممنون ${nameInput.value}! فرمت بی‌نقص پر شده.`;
    formNote.classList.remove('error');
  }
});

چک کن: ارسالِ خالی پیامِ قرمز می‌گیرد؛ با اسم، پیامِ عادی و مؤدبانه. (فرستادنِ واقعیِ پیام سرور می‌خواهد — کارِ ما اینجا بازخوردِ صادقانه به کاربر است.)

۵. چک‌لیستِ تحویل#

حرفه‌ای‌ها پروژه را با حس تمام نمی‌کنند؛ با چک‌لیست تمام می‌کنند. یکی‌یکی، واقعاً امتحان کن:

  1. کلیدِ تم کار می‌کند و بعد از ده کلیکِ پشتِ هم هم گیج نمی‌شود.
  2. در هر دو تم، همهٔ متن‌ها — حتی متنِ روی دکمه‌ها — خوانا هستند.
  3. هر سه فیلتر فهرستِ درستِ خودشان را نشان می‌دهند و فهرست هرگز دوقلو نمی‌شود.
  4. ارسالِ فرمِ خالی پیامِ خطا می‌گیرد — نه سکوت.
  5. با کلیدِ Tab به همهٔ دکمه‌ها و فرم می‌رسی، چون همه <button> و <input> واقعی‌اند (ترم ۱).
  6. با JavaScript خاموش هم صفحه محتوایش را نشان می‌دهد — فهرست در HTML هست.
  7. صفحه در عرضِ موبایل هم سالم است (ترم ۴).

اگر همهٔ تیک‌ها خورد، پروژه‌ات تحویل‌دادنی است.

و یک قدمِ آخرِ حرفه‌ای:

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

کدِ کاملِ پروژه#

// script.js

/* --- 1) kelid-e theme --- */
const themeBtn = document.querySelector('#theme-btn');

themeBtn.addEventListener('click', function () {
  document.body.classList.toggle('dark');
});

/* --- 2) filter-e prozhe-ha --- */
const webProjects = ['کارت پروفایل', 'وبلاگ شخصی', 'صفحهٔ فرود محصول'];
const funProjects = ['گالری عکس', 'کارت تبریک'];

const allProjects = [];
for (const project of webProjects) {
  allProjects.push(project);
}
for (const project of funProjects) {
  allProjects.push(project);
}

const projectList = document.querySelector('#project-list');
const allBtn = document.querySelector('#all-btn');
const webBtn = document.querySelector('#web-btn');
const funBtn = document.querySelector('#fun-btn');

allBtn.addEventListener('click', function () {
  projectList.textContent = '';
  for (const project of allProjects) {
    const item = document.createElement('li');
    item.textContent = project;
    projectList.append(item);
  }
});

webBtn.addEventListener('click', function () {
  projectList.textContent = '';
  for (const project of webProjects) {
    const item = document.createElement('li');
    item.textContent = project;
    projectList.append(item);
  }
});

funBtn.addEventListener('click', function () {
  projectList.textContent = '';
  for (const project of funProjects) {
    const item = document.createElement('li');
    item.textContent = project;
    projectList.append(item);
  }
});

/* --- 3) bazkhord-e form --- */
const form = document.querySelector('#contact-form');
const nameInput = document.querySelector('#name-input');
const formNote = document.querySelector('#form-note');

form.addEventListener('submit', function (event) {
  event.preventDefault();

  if (nameInput.value === '') {
    formNote.textContent = 'اسمت را ننوشته‌ای — فرمِ خالی جایی نمی‌رود.';
    formNote.classList.add('error');
  } else {
    formNote.textContent = `ممنون ${nameInput.value}! فرمت بی‌نقص پر شده.`;
    formNote.classList.remove('error');
  }
});

index.html را بالا دیدی؛ styles.css کامل هم در آزمایشگاه است — تماماً چیزهایی که خودت در هفت ترم ساخته‌ای.

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

کلمه تلفظ به حروف فارسی یعنی چه
capstone کَپ‌استون پروژهٔ پایانی که همهٔ آموخته‌ها را یک‌جا به‌کار می‌گیرد
ship شیپ تحویل‌دادنِ کار، وقتی چک‌لیستش واقعاً پاس شده

تمرین‌ها

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

در فصل بعد#

فصلِ بعدی وجود ندارد. اینجا پایانِ تاروپود است.

هفت ترم پیش با یک فایلِ خالی شروع کردی. حالا پشتِ سرت: صفحه‌هایی با اسکلتِ semantic، استایلی که cascade اش را می‌فهمی، layout هایی با flexbox و grid که در هر عرضی سالم می‌مانند، حالت و حرکت، کدِ تمیزِ component محور — و حالا صفحه‌ای که به کاربرش جواب می‌دهد. تار HTML بود، پود CSS، و این ترمِ آخر نخِ جانی بود که پارچه را تکان داد.

و یک حرفِ صادقانه دربارهٔ ادامهٔ راه: این ترم یک پل بود، نه یک دورهٔ JavaScript. آن‌طرفِ پل دنیای بزرگی است — function هایی که خودت تعریف می‌کنی، object ها، آرایه‌های عمیق‌تر، و بعدها framework ها — و یادگیریِ عمیقش یک خودآموزِ جداست برای وقتی که خواستی. ولی مهم‌ترین ابزار را همین حالا داری، چون هفت ترم است که داری تمرینش می‌کنی: بساز، خرابش کن، بفهم چرا خراب شد، درستش کن.

وب همیشه همین بوده: آدم‌هایی که چیزی می‌سازند و نشانش می‌دهند. از امروز تو یکی از آن‌هایی. چیزی بساز.

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

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