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

آخر این فصل میتوانی:
- با
@keyframesگامهای یک حرکت را از اول تا آخر تعریف کنی - با property
animationآن حرکت را با اسم، مدت، ریتم و تعدادِ تکرار روشن کنی - با
prefers-reduced-motionحرکت را برای کسی که آن را خاموش کرده، خاموش کنی - بگویی چرا یک حرکتِ بیپایانِ توجهگیر بدون این احترام، یک اشتباهِ دسترسپذیری است
قبل از شروع#
- از فصل ۷:
transitionو ریتمها (ease,ease-in-out) — همان ریتمها اینجا هم بهکار میروند. - از فصل ۶:
transform: scale()— گامهای حرکتمان یک دایره را بزرگ و کوچک میکنند. - از ترم ۴:
@media—prefers-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 داخلش خودش عوض میشود.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.