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

فصل ۸ از ۱۰

پیشرفت ترم
۰٪

ترم ۵ · تعامل و جزئیات

animation و احترام به کاربر

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

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

در فصل قبل با transition یاد گرفتی چطور یک تغییر را نرم کنی — ولی transition همیشه یک محرک می‌خواهد: کاربر باید ماوس را ببرد رو، یا focus بدهد، تا چیزی حرکت کند. در این فصل حرکتی می‌سازی که خودش اجرا می‌شود، بی‌آنکه کسی دستش بزند، و می‌تواند بی‌نهایت تکرار شود. به این animation می‌گویند. آخر فصل یک نشانِ «پخش زنده» داری که نقطه‌اش مثل قلب می‌تپد — و مهم‌تر از خودِ حرکت، یاد می‌گیری چطور به کاربری که حرکت را دوست ندارد احترام بگذاری.

نواری از چند فریمِ پشت‌سرِ هم که یک دایرهٔ سبز در آن‌ها کوچک و بزرگ می‌شود؛ فریم اول و آخر کوچک، فریم وسط بزرگ، و مرورگر فاصلهٔ بینشان را پُر می‌کند

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

  • با @keyframes گام‌های یک حرکت را از اول تا آخر تعریف کنی
  • با property animation آن حرکت را با اسم، مدت، ریتم و تعدادِ تکرار روشن کنی
  • با prefers-reduced-motion حرکت را برای کسی که آن را خاموش کرده، خاموش کنی
  • بگویی چرا یک حرکتِ بی‌پایانِ توجه‌گیر بدون این احترام، یک اشتباهِ دسترس‌پذیری است

قبل از شروع#

  • از فصل ۷: transition و ریتم‌ها (ease, ease-in-out) — همان ریتم‌ها اینجا هم به‌کار می‌روند.
  • از فصل ۶: transform: scale() — گام‌های حرکتمان یک دایره را بزرگ و کوچک می‌کنند.
  • از ترم ۴: @mediaprefers-reduced-motion هم یک ویژگیِ @media است، مثلِ prefers-color-scheme.

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

۱. animation چیست، و چرا با transition فرق دارد#

transition یک پل بین دو حالت است: از رنگِ الان به رنگِ بعدِ hover. کاربر باید کاری کند تا این پل رد شود. ولی خیلی چیزها روی صفحه باید بی‌آنکه کسی دستشان بزند حرکت کنند: یک نشانِ «در حال بارگذاری»، یک نقطهٔ «پخش زنده»، یک فلش که آرام بالا و پایین می‌رود. برای این‌ها animation داریم.

فرقِ کلیدی دو چیز است: animation خودش شروع می‌شود (محرک نمی‌خواهد)، و می‌تواند تکرار شود — حتی بی‌نهایت. transition فقط یک‌بار، از A به B. animation می‌تواند A → B → A → B تا ابد.

ساختنِ یک animation دو قدم دارد که در دو بخشِ بعد یکی‌یکی می‌بینیم: اول گام‌های حرکت را با @keyframes تعریف می‌کنی، بعد آن گام‌ها را با property animation به یک عنصر می‌چسبانی.

۲. @keyframes: گام‌های حرکت را تعریف کن#

@keyframes یعنی «فریم‌های کلیدی». تو فقط چند نقطهٔ مهمِ حرکت را می‌گویی — مثلاً «اولش این‌طور، وسطش آن‌طور، آخرش دوباره این‌طور» — و مرورگر خودش فاصلهٔ بینشان را نرم پُر می‌کند. هر گام را با یک درصد نشان می‌دهی: 0% شروع، 100% پایان، و هرچه بینشان بخواهی.

بیا یک تپش بسازیم: نقطه از اندازهٔ عادی شروع می‌شود، وسطِ راه بزرگ می‌شود، و آخرش به اندازهٔ عادی برمی‌گردد. این @keyframes را به CSS اضافه کن:

@keyframes heartbeat {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.6);
  }
  100% {
    transform: scale(1);
  }
}

heartbeat اسمی است که خودت گذاشته‌ای؛ می‌توانست هرچیزِ دیگری باشد. سه گام گفتیم: اولش اندازهٔ ۱ برابر، وسطش ۱٫۶ برابر، آخرش دوباره ۱. دقت کن که 0% و 100% یکی‌اند — همین باعث می‌شود وقتی تکرار شود، حرکت بی‌پرش و روان به اول برگردد.

ولی هنوز هیچ اتفاقی نمی‌افتد. @keyframes فقط دستور‌العملِ حرکت است، مثلِ یک دفترچهٔ آشپزی که هنوز کسی آشپزی نکرده. باید به یک عنصر بگویی «این دستورالعمل را اجرا کن».

۳. property animation: حرکت را روشن کن#

حالا با property animation گام‌ها را به نقطه می‌چسبانیم. این property چهار چیزِ اصلی را یک‌جا می‌گیرد:

  • اسم — کدام @keyframes؟ (heartbeat)
  • مدت — هر دورِ کامل چند طول بکشد؟ (1.5s)
  • ریتم — همان timing function فصل ۷ (ease-in-out)
  • تعدادِ تکرار — چند بار؟ عددی مثل 3، یا infinite برای بی‌نهایت

فعلاً این خط را مستقیم به .live__dot اضافه کن و ببین چه می‌شود:

.live__dot {
  animation: heartbeat 1.5s ease-in-out infinite;
}

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

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

۴. prefers-reduced-motion: احترام به کاربر#

الان یک مشکلِ جدی داریم که روی صفحهٔ خودت دیده نمی‌شود، ولی واقعی است. بعضی آدم‌ها با حرکتِ روی صفحه اذیت می‌شوند — سرگیجه، حالِ تهوع، حواس‌پرتی. برای همین در تنظیماتِ سیستم گزینه‌ای هست به‌نامِ «کاهش حرکت» (Reduce motion). وقتی کسی آن را روشن می‌کند، دارد به هر سایتی می‌گوید «لطفاً حرکتِ اضافه را برایم خاموش کن».

نقطهٔ ما این خواسته را نادیده می‌گیرد: هرکس هم که «کاهش حرکت» را روشن کرده باشد، باز این نقطه جلوِ چشمش تا ابد می‌تپد. بیا این را ببینیم. در DevTools مرورگر، بخشِ Rendering، گزینهٔ Emulate CSS prefers-reduced-motion را روی reduce بگذار (یا در تنظیماتِ سیستمت «کاهش حرکت» را روشن کن).

نگاه کن: نقطه هنوز می‌تپد. ما به مرورگر گفته‌ایم کاربر حرکت نمی‌خواهد، ولی animation ما بی‌خیالِ این خواسته کارش را می‌کند.

🔧 اگر کار نکرد: حرکت را مستقیم روی .live__dot گذاشتی، پس همیشه — حتی وقتی کاربر خاموشش خواسته — اجرا می‌شود. راهِ درست این است که animation را فقط وقتی بدهی که کاربر حرکت را رد نکرده باشد. برای این کار خطِ animation را از .live__dot بردار و داخلِ یک @media (prefers-reduced-motion: no-preference) بگذار — یعنی «فقط اگر کاربر ترجیحی برای کم‌کردنِ حرکت نداده، این حرکت را اجرا کن»:

@media (prefers-reduced-motion: no-preference) {
  .live__dot {
    animation: heartbeat 1.5s ease-in-out infinite;
  }
}

چک کن: حالا دوباره در DevTools بینِ reduce و no-preference جابه‌جا شو. با no-preference نقطه می‌تپد؛ با reduce نقطه کاملاً آرام و بی‌حرکت می‌ماند و نشان هنوز کارش را می‌کند — کاربر می‌بیند «پخش زنده» است، فقط بی‌تپش. این همان احترام است: حرکت یک تزئین است، نه اطلاعاتِ لازم، پس وقتی کاربر نخواستش، برش می‌داری بی‌آنکه چیزی از دست برود.

💡 نکته: چرا no-preference و نه اینکه حرکت را همیشه بدهیم و فقط داخلِ reduce خاموشش کنیم؟ چون این‌طور پیش‌فرضت «آرام» است و حرکت را فقط جایی که مطمئنی کاربر مانعی ندارد روشن می‌کنی. امن‌ترین حالت این است که سکوت پیش‌فرض باشد، نه سروصدا.

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

اول 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>animation</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <article class="card">
      <p class="live">
        <span class="live__dot"></span>
        در حال پخش
      </p>
      <h1>ویدیوی زنده</h1>
      <p class="desc">این نقطهٔ سبز خودش می‌تپد تا بگوید پخش زنده است.</p>
    </article>
  </body>
</html>

و styles.css:

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

:root {
  --bg: #f6edda;
  --card: #ffffff;
  --ink: #2c2114;
  --muted: #6b6150;
  --live: #55731b;
  --line: #d8ccae;
}

body {
  margin: 0;
  font-family: Tahoma, sans-serif;
  background-color: var(--bg);
  color: var(--ink);
  padding: 40px;
  line-height: 1.9;
}

.card {
  max-width: 340px;
  margin-inline: auto;
  padding: 24px;
  background-color: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px;
}

.live {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: var(--live);
  font-weight: 700;
}

.live__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background-color: var(--live);
}

.card h1 {
  margin-block: 14px 6px;
  font-size: 1.4rem;
}

.desc {
  margin: 0;
  color: var(--muted);
}

/* @keyframes -> gam-haye harekat ra tarif mikonad */
@keyframes heartbeat {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.6);
  }
  100% {
    transform: scale(1);
  }
}

/* faghat vaghti karbar harekat ra rad nakarde bashad */
@media (prefers-reduced-motion: no-preference) {
  .live__dot {
    animation: heartbeat 1.5s ease-in-out infinite;
  }
}

دو حالت کنارِ هم: سمتِ راست یک نقطهٔ سبز با چند کمانِ حرکت دورش که تپش را نشان می‌دهد، سمتِ چپ همان نقطه بی‌هیچ کمان و آرام؛ بالای هر دو یک کلیدِ سیستم که یکی حرکت را اجازه می‌دهد و دیگری کم می‌کند

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

animation یکی از همان جاهایی است که AI هم بیشترین کمک را می‌کند و هم راحت اشتباه می‌گوید — چون کدش «درست به‌نظر می‌رسد» ولی رفتارش را فقط با دیدن در مرورگر می‌فهمی. سه قانونِ ثابتِ کار با AI در کلِ این خودآموز اینهاست:

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

کاربردِ خوب. بهترین استفادهٔ AI در این فصل یک بازبینیِ دسترس‌پذیری است — دقیقاً همان چیزی که تازه یاد گرفتی:

🤖 با AI: بعد از اینکه خودت animation را نوشتی، CSS ات را به دستیار AI ات بده و بپرس: «آیا این حرکت به prefers-reduced-motion احترام می‌گذارد؟ اگر نه، چطور درستش کنم؟». این یک جفت‌چشمِ دومِ عالی است. ولی جوابش را همان‌جا در آزمایشگاه با Emulate prefers-reduced-motion خودت بسنج — ببین واقعاً با reduce حرکت می‌ایستد یا نه.

کاربردِ اشتباه. حالا نمونه‌ای که AI با اعتمادبه‌نفسِ کامل غلط می‌گوید. فرض کن می‌خواهی تپش را با یک ثانیه تأخیر شروع کنی و از AI می‌پرسی. خیلی وقت‌ها چیزی شبیه این می‌دهد و مطمئن توضیحش می‌دهد: «این یعنی ۱ ثانیه صبر کن، بعد یک تپشِ ۲ ثانیه‌ای»:

.live__dot {
  animation: heartbeat 1s 2s infinite;
}

جمله‌اش منطقی به‌نظر می‌رسد، ولی برعکسِ چیزی است که ادعا می‌کند. در آزمایشگاه امتحانش کن: تپش تند است و دو ثانیه هم اولش صبر می‌کند. چرا؟ با نکتهٔ بخش ۳ می‌توانی ردش کنی: وقتی دو مقدارِ زمانی بنویسی، اولی مدت است و دومی تأخیر. پس 1s 2s یعنی مدت ۱ ثانیه و تأخیر ۲ ثانیه — دقیقاً برعکسِ چیزی که AI گفت.

درستش این است که ترتیب را بچرخانی — heartbeat 2s 1s infinite (مدت ۲ ثانیه، تأخیر ۱ ثانیه) — یا برای اینکه اصلاً جای شک نماند، به‌جای shorthand از نامِ کاملشان استفاده کنی: animation-duration: 2s; و animation-delay: 1s;. این بهترین درسِ «AI هم اشتباه می‌کند» است: AI جمله‌ای ساخت که قاطع و درست به‌نظر می‌رسید، و تنها چیزی که لوش داد، دیدنِ رفتارِ واقعی در مرورگر بود. اول خودت، بعد AI.

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

کلمه تلفظ به حروف فارسی یعنی چه
animation اَنیمِیشِن حرکتی که خودش اجرا می‌شود و می‌تواند تکرار شود، بی‌آنکه محرکی بخواهد
@keyframes کی‌فریمز دستورالعملِ حرکت؛ گام‌های کلیدی از 0% تا 100%
iteration ایتِریشِن تعدادِ تکرارِ یک animation؛ infinite یعنی بی‌نهایت
prefers-reduced-motion پریفِرز ریدیوسد موشِن ویژگیِ @media که می‌گوید کاربر حرکت را کم خواسته یا مانعی ندارد

تمرین‌ها

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

در فصل بعد#

الان می‌توانی حرکت بسازی و بااحترام به‌کارش ببری. تا اینجا هرچه ساختیم یک‌طرفه بود: صفحه واکنش نشان می‌داد ولی حالتش را نگه نمی‌داشت. فصل بعد یک selectorِ تازه و شگفت‌انگیز را می‌بینیم: :has(). با آن یک عنصر می‌تواند بر اساسِ فرزندش استایل بگیرد — و همین در را به تعاملِ واقعی، بدونِ یک خط JavaScript، باز می‌کند: کارتی که با تیک‌خوردنِ checkbox داخلش خودش عوض می‌شود.

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

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