بستن

آموزش کامل Responsive Design در CSS

responsive design در css شامل Media Query و Container Query

این جلسه ادامه دوره کامل آموزش CSS آوای وبه. تو جلسه نهم CSS Grid رو کامل یاد گرفتیم. حالا وقتشه یاد بگیریم responsive design در css یعنی چی، یعنی چطور یه سایت رو طوری بسازیم که چه رو موبایل ۳۶۰ پیکسلی باز بشه، چه رو مانیتور ۲۷ اینچی، همیشه خوب و قابل‌استفاده بمونه.

آنچه در این مقاله می‌خوانید:

📜 چرا اصلاً به طراحی ریسپانسیو نیاز داریم؟

تا چند سال پیش، بیشتر سایت‌ها فقط برای دسکتاپ طراحی می‌شدن. ولی امروز، بخش بزرگی (اغلب اکثریت) ترافیک سایت‌ها از موبایله. اگه سایتت فقط برای یه اندازه‌ی صفحه طراحی شده باشه، رو دستگاه‌های دیگه یا متن‌ها روی هم می‌افتن، یا کاربر مجبوره افقی اسکرول کنه، یا دکمه‌ها آنقدر کوچیکن که با انگشت قابل لمس نیستن. طراحی ریسپانسیو دقیقاً یعنی یه چیدمان واحد که خودش رو با اندازه‌ی صفحه‌ی هر کاربر تطبیق می‌ده.

🎯 اهداف یادگیری این جلسه

  • با Media Query و همه‌ی نوع‌ها و ویژگی‌هاش کامل آشنا بشی
  • فرق Mobile-First و Desktop-First رو با مثال بفهمی
  • واحدهای انعطاف‌پذیر (rem، %، vw، vh) رو حرفه‌ای به‌کار ببری
  • با Container Query — جدیدترین ابزار ریسپانسیو — آشنا بشی
  • یه مرجع کامل از Breakpointهای استاندارد صنعت داشته باشی

📱 اول از همه: تگ Viewport رو فراموش نکن

<meta name="viewport" content="width=device-width, initial-scale=1.0">

این تگ باید تو بخش <head> هر صفحه‌ای باشه بدونش، مرورگر موبایل صفحه رو انگار یه دسکتاپ کوچیک‌شده نمایش می‌ده (مثلاً کل صفحه رو با فونت ریز نشون می‌ده)، نه این‌که واقعاً چیدمان رو تطبیق بده. width=device-width می‌گه «عرض صفحه رو برابر عرض واقعی دستگاه در نظر بگیر»، و initial-scale=1.0 می‌گه «بدون زوم شروع کن».

🔍 Media Query — قلب responsive design در css

@media (max-width: 768px) {
  .container {
    flex-direction: column;
  }
}

این کد می‌گه: «فقط وقتی عرض صفحه‌ی نمایش ۷۶۸ پیکسل یا کمتره، این استایل‌ها رو اعمال کن». هر چیزی داخل این آکولاد، فقط تو اون شرط فعال می‌شه؛ خارج از اون شرط، هیچ اثری نداره.

عملگرهای پایه‌ای Media Query

عملگرتوضیح سادهمثال
max-widthوقتی عرض صفحه از این عدد کمتر یا مساوی باشه@media (max-width: 768px)
min-widthوقتی عرض صفحه از این عدد بیشتر یا مساوی باشه@media (min-width: 1024px)
max-height / min-heightهمون منطق بالا ولی برای ارتفاع صفحه@media (min-height: 600px)
orientationجهت صفحه: عمودی (portrait) یا افقی (landscape)@media (orientation: landscape)
prefers-color-schemeحالت روشن یا تاریک سیستم‌عامل کاربر@media (prefers-color-scheme: dark)
prefers-reduced-motionوقتی کاربر تو تنظیمات سیستم، انیمیشن کمتر خواسته (برای دسترس‌پذیری)@media (prefers-reduced-motion: reduce)

ترکیب چند شرط با and

@media (min-width: 600px) and (max-width: 900px) {
  .sidebar {
    display: none;
  }
}

این یعنی: «فقط وقتی عرض بین ۶۰۰ تا ۹۰۰ پیکسله (نه کمتر، نه بیشتر)». این دقیقاً چیزیه که برای هدف‌گیری یه بازه‌ی خاص (مثلاً فقط تبلت) لازمه.

📐 Mobile-First در برابر Desktop-First

این یه تصمیم معماری مهمه که باید از همون اول پروژه بگیری:

Desktop-First (روش قدیمی‌تر)

.container {
  display: flex; /* استایل پیش‌فرض برای دسکتاپ */
}

@media (max-width: 768px) {
  .container {
    display: block; /* override برای موبایل */
  }
}

اول استایل دسکتاپ رو می‌نویسی، بعد با max-width برای صفحه‌های کوچیک‌تر override می‌کنی.

Mobile-First (روش استاندارد امروز)

.container {
  display: block; /* استایل پیش‌فرض برای موبایل */
}

@media (min-width: 768px) {
  .container {
    display: flex; /* ارتقا برای صفحه‌های بزرگ‌تر */
  }
}

برعکس روش قبلی: اول استایل پایه رو برای کوچیک‌ترین صفحه (موبایل) می‌نویسی، بعد با min-width برای صفحه‌های بزرگ‌تر «ارتقاش» می‌دی.

چرا Mobile-First امروز استاندارده؟ چون اکثریت ترافیک وب از موبایله، منطقی‌تره که تجربه‌ی پیش‌فرض (بدون هیچ Media Query) برای اکثریت کاربرا بهینه باشه. همچنین این روش معمولاً کد CSS کمتر و سبک‌تری تولید می‌کنه، چون قوانین پایه ساده‌ترن و فقط برای صفحه‌های بزرگ‌تر پیچیدگی اضافه می‌کنی.

📏 واحدهای انعطاف‌پذیر — پایه‌ی واقعی ریسپانسیو بودن

حتی بدون هیچ Media Query‌ای، انتخاب واحد درست می‌تونه خیلی از مشکلات ریسپانسیو رو از اول حل کنه.

واحدتوضیح سادهبهترین کاربرد
pxپیکسل مطلق، هیچ‌وقت با اندازه‌ی صفحه یا فونت کاربر تغییر نمی‌کنهBorder، سایه، جاهایی که باید دقیقاً ثابت بمونن
%درصدی از اندازه‌ی والدعرض ستون‌ها نسبت به کانتینر
emنسبت به سایز فونت والد مستقیم همون عنصرفاصله‌گذاری داخل یه کامپوننت خاص (می‌تونه تو تودرتوها پیچیده بشه)
remنسبت به سایز فونت ریشه (تگ html)، صرف‌نظر از تودرتوییفونت‌ها و فاصله‌گذاری‌های کلی صفحه (پیشنهاد استاندارد صنعت)
vw۱٪ از عرض کامل viewport (پنجره‌ی نمایش)فونت یا عنصری که باید دقیقاً با عرض صفحه رشد کنه
vh۱٪ از ارتفاع کامل viewportبخش‌هایی که باید دقیقاً ارتفاع صفحه رو پر کنن (مثل یه Hero Section تمام‌صفحه)

💡 فرق مهم em و rem با مثال: اگه font-size تگ html برابر ۱۶px باشه، هر جا 1rem بنویسی، همیشه دقیقاً ۱۶px می‌شه، فرقی نداره چند لایه تو در تو باشه. ولی 1em نسبت به فونت والد مستقیم حساب می‌شه، پس اگه یه <div> با font-size: 20px داخل یه <div> دیگه با font-size: 20px باشه، مقدار em‌ها تو هرکدوم متفاوت محاسبه می‌شه، همین باعث سردرگمی زیادی برای مبتدی‌ها می‌شه.

🎨 clamp() — تایپوگرافی روان بدون حتی یه Media Query

h1 {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

این تابع سه مقدار می‌گیره: **حداقل**، **مقدار ایده‌آل** (که با صفحه رشد می‌کنه)، و **حداکثر**. یعنی این کد می‌گه: «فونت رو بذار روی ۴٪ عرض صفحه، ولی هیچ‌وقت از ۱.۵rem کوچیک‌تر و از ۳rem بزرگ‌تر نشه». نتیجه: تایتل به‌طور نرم و پیوسته با عرض صفحه رشد می‌کنه، بدون این‌که مجبور باشی برای هر breakpoint یه مقدار جدا بنویسی.

🖼️ تصاویر ریسپانسیو

img {
  max-width: 100%;
  height: auto;
}

این دو خط ساده، یکی از پرکاربردترین و ضروری‌ترین قوانین ریسپانسیوه. max-width: 100% می‌گه «تصویر هیچ‌وقت از عرض والدش بزرگ‌تر نشه» (پس رو موبایل از صفحه بیرون نمی‌زنه)، و height: auto باعث می‌شه وقتی عرض کوچیک می‌شه، ارتفاع هم به‌نسبت درست کوچیک بشه (نسبت تصویر خراب نشه).

🆕 Container Query — جدیدترین و انقلابی‌ترین ابزار ریسپانسیو

تا الان همه‌چیز رو بر اساس اندازه‌ی کل صفحه (viewport) تصمیم گرفتیم. ولی یه مشکل واقعی هست: یه کامپوننت (مثل یه کارت) ممکنه تو یه سایدبار باریک باشه یا تو یه بخش عریض — و اندازه‌ی واقعی صفحه‌ی مرورگر اصلاً به این ربطی نداره. Container Query دقیقاً همین مشکل رو حل می‌کنه: به‌جای واکنش به اندازه‌ی viewport، عنصر به اندازه‌ی والد مستقیمش واکنش نشون می‌ده.

.card-container {
  container-type: inline-size;
  container-name: card;
}

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

بذار این کد رو معنی کنیم: اول با container-type: inline-size می‌گیم «این عنصر (والد) رو به‌عنوان یه کانتینر قابل ‌اندازه‌گیری در نظر بگیر». بعد با @container (به‌جای @media) می‌گیم «اگه عرض همین کانتینر (نه کل صفحه) از ۴۰۰px بیشتر شد، کارت رو به Flex تبدیل کن». نتیجه: همون کامپوننت کارت، اگه تو یه سایدبار باریک باشه یه شکل نمایش داده می‌شه، و اگه همون دقیقاً کامپوننت تو یه بخش عریض باشه، شکل دیگه‌ای، بدون این‌که به اندازه‌ی کل صفحه‌ی مرورگر ربطی داشته باشه.

⚠️ این ویژگی نسبتاً جدیده؛ پشتیبانی مرورگرهای مدرن خوبه ولی برای پروژه‌های حساس، پشتیبانی مرورگرهای هدفت رو چک کن.

🛍️ مثال واقعی: کارت محصول ریسپانسیو کامل

.product-card {
  display: flex;
  flex-direction: column;
  padding: 1rem;
}

@media (min-width: 768px) {
  .product-card {
    flex-direction: row;
    align-items: center;
  }
}

.product-card img {
  max-width: 100%;
  height: auto;
}

.product-card h3 {
  font-size: clamp(1rem, 2.5vw, 1.5rem);
}

رو موبایل، این کارت تصویر و متن رو زیر هم (ستونی) نشون می‌ده؛ از ۷۶۸px به بالا، خودکار به چیدمان کنار هم (ردیفی) تغییر می‌کنه، همه با یه Media Query ساده و رویکرد Mobile-First.

مقایسه کارت محصول ریسپانسیو در موبایل و دسکتاپ

📊 جدول Breakpointهای استاندارد صنعت

هیچ عدد «رسمی» یا اجباری برای Breakpoint وجود نداره، ولی این اعداد بیشترین استفاده رو تو پروژه‌های واقعی دنیا دارن:

دستگاهعرض تقریبیMedia Query نمونه
موبایل کوچیکتا ۴۸۰pxپیش‌فرض (بدون Media Query، چون Mobile-First هستیم)
موبایل بزرگ / فبلت۴۸۰px تا ۶۴۰px@media (min-width: 480px)
تبلت۶۴۰px تا ۱۰۲۴px@media (min-width: 768px)
دسکتاپ۱۰۲۴px تا ۱۴۴۰px@media (min-width: 1024px)
دسکتاپ بزرگبالای ۱۴۴۰px@media (min-width: 1440px)

⚠️ اشتباهات رایج در طراحی ریسپانسیو

  • فراموش کردن تگ viewport: بدون این تگ، هیچ Media Query‌ای درست کار نمی‌کنه.
  • قاطی کردن Mobile-First و Desktop-First تو یه پروژه: ترکیب min-width و max-width بدون برنامه‌ریزی باعث تداخل و باگ‌های عجیب می‌شه.
  • Breakpoint گذاشتن بر اساس دستگاه خاص، نه محتوا: به‌جای این‌که فکر کنی «آیفون ۱۲ چند پیکسله»، به این فکر کن «محتوای من کجا شکسته می‌شه و زشت می‌شه» — همون‌جا Breakpoint بذار.
  • استفاده از px برای فونت به‌جای rem: باعث می‌شه اگه کاربر تو تنظیمات مرورگرش سایز فونت پیش‌فرض رو عوض کنه، سایت هیچ واکنشی نشون نده (مشکل دسترس‌پذیری).
  • فراموش کردن srcset برای تصاویر بزرگ: باعث می‌شه موبایل مجبور بشه یه فایل چندمگابایتی دانلود کنه که اصلاً بهش نیاز نداره.
  • استفاده از 100vh به‌جای 100dvh رو موبایل: باعث پرش ناخواسته‌ی محتوا موقع نمایان/پنهان شدن نوار آدرس مرورگر می‌شه.
  • فراموش کردن srcset برای تصاویر بزرگ: باعث می‌شه موبایل مجبور بشه یه فایل چندمگابایتی دانلود کنه که اصلاً بهش نیاز نداره.

📋 جدول جامع: کدوم ابزار برای کدوم سناریو؟

ابزارواکنش به چی؟بهترین سناریو
Media Queryاندازه‌ی کل viewportچیدمان کلی صفحه
Container Queryاندازه‌ی والد مستقیم عنصرکامپوننت‌های قابل‌استفاده‌ی مجدد در جاهای مختلف
clamp()عرض viewport (به‌صورت پیوسته)تایپوگرافی روان بدون breakpoint مشخص
rem / %ذاتاً انعطاف‌پذیرنفاصله‌گذاری و اندازه‌ی پایه

🖨️ فراتر از صفحه‌نمایش: Media Feature‌های کمتر شناخته‌شده

اکثر آموزش‌ها فقط width رو پوشش می‌دن، ولی Media Query می‌تونه به چیزهای دیگه‌ای هم واکنش نشون بده که خیلی وقت‌ها فراموش می‌شن:

Media Featureتوضیحمثال
hoverآیا دستگاه اصلاً قابلیت هاور کردن داره؟ (موبایل و تبلت معمولاً ندارن)@media (hover: hover)
pointerدقت وسیله‌ی اشاره‌گر: fine (ماوس)، coarse (انگشت)@media (pointer: coarse)
resolutionتراکم پیکسل صفحه (برای تصاویر retina)@media (min-resolution: 2dppx)
printوقتی کاربر صفحه رو چاپ می‌کنه، نه وقتی رو صفحه‌نمایش می‌بینه@media print
prefers-contrastکاربرانی که تو سیستم‌عامل، کنتراست بالاتر خواستن (دسترس‌پذیری)@media (prefers-contrast: more)
prefers-reduced-dataکاربرانی که خواستن داده‌ی کمتری مصرف بشه (مثلاً تصاویر سبک‌تر بارگذاری بشه)@media (prefers-reduced-data: reduce)

مثال عملی خیلی کاربردی برای hover و pointer: فرض کن یه دکمه داری که با :hover رنگش عوض می‌شه. رو موبایل، چون هاور معنی نداره، بعضی مرورگرها بعد از هر تپ، حالت هاور رو «گیر» نگه می‌دارن (چسبناک می‌مونه) تا کاربر دوباره جای دیگه‌ای لمس کنه. با این کد جلوش رو می‌گیری:

@media (hover: hover) and (pointer: fine) {
  button:hover {
    background: darkblue;
  }
}

یعنی: «فقط وقتی دستگاه واقعاً ماوس داره و هاور معنی داره، این استایل رو فعال کن» — رو موبایل و تبلت اصلاً این قانون بارگذاری نمی‌شه.

🖼️ تصاویر ریسپانسیو واقعی: فراتر از max-width: 100%

max-width: 100%; height: auto; فقط جلوی سرریز شدن تصویر رو می‌گیره، ولی مشکل بزرگ‌تری حل‌نشده می‌مونه: چرا باید یه موبایل با صفحه‌ی کوچیک، دقیقاً همون فایل تصویر ۲۰۰۰ پیکسلی‌ای رو دانلود کنه که یه مانیتور ۲۷ اینچی دانلود می‌کنه؟ این هم حجم اضافه، هم سرعت پایین‌تر. راه‌حل واقعی srcsetه:

<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="توضیح تصویر">

بذار خط‌به‌خط معنی کنیم: srcset به مرورگر می‌گه «این‌ها همه نسخه‌های مختلف همین عکس‌ان، با این عرض‌ها (400، 800، 1200 پیکسل)». sizes می‌گه «رو صفحه‌های تا ۶۰۰px، این عکس کل عرض صفحه (100vw) رو می‌گیره؛ رو صفحه‌های بزرگ‌تر، فقط نصف عرض (50vw) رو می‌گیره». خود مرورگر، با ترکیب این دو اطلاعات و تراکم پیکسل صفحه‌ی کاربر، هوشمندانه تصمیم می‌گیره کدوم فایل رو دانلود کنه — بدون هیچ جاوااسکریپتی.

برای حالت‌های پیچیده‌تر (مثلاً کراپ کاملاً متفاوت رو موبایل در مقابل دسکتاپ، نه فقط اندازه‌ی متفاوت)، از <picture> استفاده می‌شه:

<picture>
  <source media="(max-width: 600px)" srcset="photo-mobile-crop.jpg">
  <source media="(min-width: 601px)" srcset="photo-desktop-crop.jpg">
  <img src="photo-desktop-crop.jpg" alt="توضیح تصویر">
</picture>

📏 واحدهای viewport مدرن: مشکل نوار آدرس موبایل

یه مشکل واقعی و آزاردهنده با vh: رو خیلی از مرورگرهای موبایل، وقتی کاربر اسکرول می‌کنه، نوار آدرس بالای صفحه کوچیک یا مخفی می‌شه — که یعنی ارتفاع واقعی viewport لحظه‌به‌لحظه عوض می‌شه. این باعث می‌شه یه عنصر با height: 100vh گاهی یه‌کم بزرگ‌تر از صفحه‌ی واقعی به‌نظر برسه (یا یه اسکرول‌بار عمودی ناخواسته ایجاد کنه). واحدهای جدیدتر این مشکل رو حل کردن:

واحدتوضیح
svh (Small Viewport Height)کوچیک‌ترین حالت ممکن viewport — یعنی وقتی نوار آدرس کامل نمایان‌ه
lvh (Large Viewport Height)بزرگ‌ترین حالت ممکن — وقتی نوار آدرس مخفی شده
dvh (Dynamic Viewport Height)پویا؛ خودش رو لحظه‌به‌لحظه با تغییر واقعی نوار آدرس تطبیق می‌ده
.hero-section {
  height: 100dvh; /* به‌جای 100vh قدیمی */
}

برای بخش‌هایی که باید دقیقاً تمام‌صفحه به‌نظر برسن (مثل یه Hero Section)، dvh امروز انتخاب درست‌تریه.

مقایسه رفتار vh و dvh هنگام تغییر نوار آدرس مرورگر موبایل

🧪 چطور واقعاً ریسپانسیو بودن سایتت رو تست کنی؟

  • Chrome/Firefox DevTools: با کلید F12 یا راست‌کلیک → Inspect، آیکون موبایل/تبلت (Device Toolbar) رو بزن تا اندازه‌های مختلف دستگاه رو شبیه‌سازی کنی
  • تست با کشیدن لبه‌ی پنجره:
  • Google Mobile-Friendly Test: ابزار رایگان گوگل که می‌گه آیا صفحه از دید گوگل، برای موبایل مناسبه یا نه
  • تست رو دستگاه واقعی: شبیه‌سازی DevTools همیشه ۱۰۰٪ دقیق نیست؛ قبل از انتشار نهایی، حتماً رو یه موبایل و تبلت واقعی هم چک کن

🧪 تمرین این جلسه

یه navbar بساز که رو موبایل ستونی (منوی همبرگری فرضی) و از ۷۶۸px به بالا ردیفی بشه. بعد یه تایتل با clamp() بساز که بین ۱.۵rem و ۳rem به‌نرمی رشد کنه. در پایان، یه کارت با Container Query بساز که وقتی والدش عریض‌تر از ۴۰۰px بود، به Flex ردیفی تبدیل بشه.

❓ سوالات متداول

فرق Media Query و Container Query چیه؟

Media Query به اندازه‌ی کل صفحه‌ی مرورگر (viewport) واکنش نشون می‌ده؛ Container Query به اندازه‌ی والد مستقیم همون عنصر، صرف‌نظر از اندازه‌ی کل صفحه.

چرا باید Mobile-First بنویسم، مگه فرقی می‌کنه؟

از نظر فنی هر دو روش کار می‌کنن، ولی چون اکثریت کاربرا از موبایل بازدید می‌کنن، منطقی‌تره تجربه‌ی پیش‌فرض (بدون هیچ Media Query) برای اونا بهینه باشه؛ همچنین معمولاً کد کمتر و تمیزتری تولید می‌کنه.

باید از px استفاده کنم یا rem؟

برای فونت و فاصله‌گذاری کلی، rem بهتره چون به تنظیمات دسترس‌پذیری کاربر احترام می‌ذاره؛ px رو بیشتر برای جاهایی نگه دار که باید دقیقاً ثابت بمونن، مثل border.

فرق vh و dvh چیه؟

vh ارتفاع viewport رو یه‌بار و ثابت حساب می‌کنه؛ dvh خودش رو با تغییرات واقعی (مثل نمایان/پنهان‌شدن نوار آدرس موبایل) لحظه‌به‌لحظه تطبیق می‌ده، پس برای بخش‌های تمام‌صفحه رو موبایل دقیق‌تره.

🎯 جمع‌بندی و قدم بعدی

حالا می‌دونی ریسپانسیو در CSS چطور کار می‌کنه: از Media Query و Mobile-First گرفته تا واحدهای انعطاف‌پذیر و جدیدترین ابزار یعنی Container Query. جلسه بعد می‌ریم سراغ Typography — تایپوگرافی حرفه‌ای در CSS.

جلسه یازدهم: آموزش کامل تایپوگرافی در CSS ←

💬 نوبت توئه!

Mobile-First کار می‌کردی یا Desktop-First؟ Container Query رو قبلاً امتحان کرده بودی؟ همین پایین برامون بنویس 🚀

شهرام نجفی

شهرام نجفی

شهرام نجفی — بیش از ۱۵ سال طراحی و توسعهٔ وب به‌صورت فریلنس. قالب اختصاصی آوای وب را از صفر نوشته‌ام. مطالب این سایت را بر پایهٔ مستندات رسمی MDN و WHATWG می‌نویسم و قبل از انتشار در مرورگرهای واقعی تست می‌کنم. نمونه‌کارهایم در shahramnajafi.com است.

نظر شما در این مورد چیه؟