كن مطوّر واجهات أمامية محترفا
2 دقيقة قراءة

التصميم المتجاوب وMedia Queries: واجهات تناسب كل شاشة

دليل عملي كامل للتصميم المتجاوب — وسم viewport وmedia queries بنهج mobile-first وmin/max-width والنطاقات ونقاط التوقف التي تتبع المحتوى والخطوط السائلة بـ clamp() والصور المتجاوبة واستعلامات prefers-* واستعلامات الحاوية — مع تمارين عملية.

صفحة الويب بلا حجم ثابت. نفس الـ HTML يصل إلى هاتف بعرض 360px، وحاسوب محمول بعرض 1440px، وكل ما بينهما — إضافة إلى الطباعة والوضع الداكن ومستخدمين يفضّلون ألا يروا حركة. التصميم المتجاوب هو فن كتابة تخطيط واحد يتكيّف مع كل هؤلاء، وmedia queries هي المفتاح الرئيسي لـ "طبّق هذه الأنماط فقط ضمن هذه الشروط".

الـ media query تطرح على المتصفح سؤال نعم/لا عن البيئة — "هل عرض نافذة العرض 768px على الأقل؟" — وتطبّق كتلة CSS فقط عندما يكون الجواب نعم.

أولا، الأساس: وسم viewport

قبل أن تعمل أي media query على هاتف، تحتاج هذا في <head>:

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

بدونه، تتظاهر متصفحات الهاتف بعرض ~980px وتقلّص الصفحة كلها لتتسع — فلا تتطابق أبدا استعلامك max-width: 600px المكتوب بعناية. هذا السطر الواحد يقول للمتصفح "استخدم عرض الجهاز الحقيقي". وهو السبب الأشيع لـ "CSS المتجاوب لا يفعل شيئا على الهاتف".

تشريح media query

@media (min-width: 768px) {
  .layout {
    grid-template-columns: 220px 1fr;
  }
}
  • @media — القاعدة (at-rule).
  • (min-width: 768px)الشرط (ميزة وسائط وقيمة).
  • الكتلة بالداخل تُطبَّق فقط عندما يكون الشرط صحيحا.

يمكنك دمج الشروط بـ and، وسرد بدائل بفاصلة (تعني "أو")، والنفي بـ not:

@media (min-width: 768px) and (max-width: 1199px) { /* نطاق الجهاز اللوحي */ }
@media (max-width: 600px), (orientation: portrait) { /* أيّهما */ }
@media (prefers-color-scheme: dark) { /* الوضع الداكن */ }

Mobile-First: اكتب الشاشة الصغيرة، ثم عزّز

الأنماط الافتراضية بلا استعلام يجب أن تستهدف أصغر شاشة؛ ثم تضيف media queries تعقيدا كلما سمحت المساحة. هذا نهج "mobile-first" يستخدم استعلامات min-width تتراكم فوق بعضها:

/* الأساس: الهاتف — عمود واحد، الافتراضي */
.cards { display: grid; grid-template-columns: 1fr; gap: 16px; }

/* الجهاز اللوحي وما فوق: عمودان */
@media (min-width: 768px) {
  .cards { grid-template-columns: 1fr 1fr; }
}

/* سطح المكتب وما فوق: أربعة أعمدة */
@media (min-width: 1200px) {
  .cards { grid-template-columns: repeat(4, 1fr); }
}
phonetabletdesktop
mobile-first يعني أن الأنماط الأساسية هي تخطيط الهاتف؛ وكل استعلام min-width يضيف أعمدة كلما كبرت الشاشة. تتراكم الأنماط صعودا بدل أن تُلغى نزولا.

ينتصر mobile-first لأن أبسط تخطيط هو الافتراضي (جيد لأكثر الأجهزة محدودية)، ولأن كل استعلام إضافي محض — أسهل في الفهم من استعلامات max-width التي تطرح من أساس سطح المكتب.

min-width وmax-width وصيغة النطاق

  • min-width: X — تُطبَّق حين يكون عرض نافذة العرض X أو أوسع (mobile-first، بناء صعودا).
  • max-width: X — تُطبَّق حين يكون العرض X أو أضيق (desktop-first، تقليص نزولا).

فخ دقيق: max-width وmin-width المتداخلان عند نفس البكسل يتطابقان معا عند ذلك العرض بالضبط. اختر اتجاها واحدا (عادة min-width) والتزم به. وتدعم المتصفحات الحديثة أيضا صيغة نطاق أنظف:

@media (width >= 768px) { /* مثل min-width: 768px */ }
@media (768px <= width < 1200px) { /* نطاق نظيف وغير ملتبس */ }

نقاط التوقف تتبع المحتوى لا الأجهزة

لا توجد قائمة قانونية لنقاط التوقف "الصحيحة"، ومطاردة عروض أجهزة بعينها (هذا آيفون، ذاك آيباد) لعبة خاسرة — الأجهزة تتغير باستمرار. أضف نقطة توقف حيث يبدأ تخطيطك بالتشوّه، لا حيث يقع هاتف شائع. غيّر حجم المتصفح ببطء؛ واللحظة التي يطول فيها سطر نص أكثر من اللازم أو تضيق فيها البطاقات هي نقطة توقفك. النقاط الشائعة كـ 600px و900px و~1200px جيدة كافتراضيات، لكن دع المحتوى يحرّكها.

أبعد من العرض: ميزات وسائط أخرى

العرض هو العنوان، لكن media queries تستطيع السؤال عن أكثر بكثير:

@media (orientation: landscape) { /* أعرض من ارتفاعه */ }
@media (prefers-color-scheme: dark) { /* المستخدم يريد واجهة داكنة */ }
@media (prefers-reduced-motion: reduce) { /* المستخدم لا يحب الحركة */ }
@media (hover: hover) and (pointer: fine) { /* لديه فأرة حقيقية */ }
@media print { /* الطباعة / إخراج PDF */ }

اثنان منها يهمّان كل موقع جاد:

  • prefers-reduced-motion: reduce — لُفّ الحركات غير الضرورية ليحصل المستخدمون الحسّاسون للحركة على تجربة هادئة. إنه أساس لإمكانية الوصول، لا رفاهية.
  • prefers-color-scheme — احترم إعداد النظام الفاتح/الداكن بدل فرض سمة واحدة.
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

التحجيم السائل: غالبا لا تحتاج استعلاما أصلا

الغريزة الحديثة هي جعل الأشياء تتحجّم بسلاسة مستمرة وحجز media queries للتغييرات التخطيطية الحقيقية. ثلاث أدوات تقوم بمعظم هذا:

  • clamp(min, preferred, max) — خطوط ومسافات سائلة تنمو مع نافذة العرض لكنها لا تتجاوز حدودها أبدا.
  • min() / max() — اختر الأصغر/الأكبر من قيمتين مباشرة.
  • الوحدات السائلةvw وvh و% تتحجّم مع نافذة العرض؛ وrem تتحجّم مع إعداد خط المستخدم.
h1 {
  /* لا أصغر من 1.75rem، ولا أكبر من 3rem، سائل بينهما */
  font-size: clamp(1.75rem, 1rem + 3vw, 3rem);
}
.container {
  /* بكامل العرض حتى يبلغ 1100px ثم يُحدّ — بلا حاجة لـ media query */
  width: min(100% - 2rem, 1100px);
  margin-inline: auto;
}

عنوان بـ clamp() يحلّ محل ثلاثة أو أربعة استعلامات حجم خط بسطر واحد. الجأ إلى الاستعلامات حين يجب أن تتغير البنية (عمود واحد ← ثلاثة)؛ والجأ إلى clamp()/min() حين تحتاج قيمة أن تتحجّم فقط.

الصور المتجاوبة

التخطيط ليس كل القصة — الصور تحتاج أن تتكيّف أيضا:

img { max-width: 100%; height: auto; }   /* الأساس: لا تتجاوز أبدا */

لتقديم ملفات بأحجام مختلفة، تتيح srcset للمتصفح اختيار الدقة المناسبة، ويتيح <picture> تبديل الصور (أو توجيهها فنيا) حسب الشرط:

<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
  sizes="(min-width: 768px) 50vw, 100vw"
  alt="..."
/>

يقرأ المتصفح sizes، ويحسب العرض الذي ستُعرض به الصورة، وينزّل أصغر ملف يبقى واضحا — موفّرا حزمة البيانات على الهواتف تلقائيا.

استعلامات الحاوية: الاستجابة للأب لا للصفحة

تسأل media queries عن نافذة العرض. لكن المكوّن غالبا يهتم بعرض حاويته لا النافذة كلها — بطاقة في شريط جانبي ضيق وبطاقة مماثلة في عمود رئيسي واسع يجب أن تبدوا مختلفتين رغم تطابق نافذة العرض. استعلامات الحاوية تحل هذا بالضبط:

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

/* صمّم البطاقة بناء على عرض حاويتها هي */
@container (min-width: 400px) {
  .card { display: grid; grid-template-columns: 120px 1fr; }
}

هذا أكبر تحوّل في التصميم المتجاوب منذ media queries: تصبح المكوّنات مكتفية ذاتيا حقا، تصمّم نفسها حسب المساحة الممنوحة لها لا حسب حجم الصفحة. واستعلامات الحاوية مدعومة جيدا في المتصفحات الحالية — استخدمها للمكوّنات القابلة لإعادة الاستخدام، وأبقِ media queries لبنية مستوى الصفحة.

أخطاء شائعة

  • نسيان وسم viewport، ثم التساؤل لماذا يتجاهل الهاتف استعلاماتك.
  • خلط أنماط min-width وmax-width حتى تتصارع التجاوزات — اختر mobile-first والتزم به.
  • مطاردة عروض أجهزة بعينها بدل ترك المحتوى يملي نقاط التوقف.
  • كتابة أربعة استعلامات حجم خط بينما clamp() واحد يكفي.
  • التحريك بحرية دون احترام prefers-reduced-motion.
  • استخدام media query لنافذة العرض بينما المكوّن احتاج فعلا استعلام حاوية.
  • نسيان img { max-width: 100% }، فتفجّر صورة كبيرة التخطيط.

تمارين

غيّر حجم المتصفح (أو شريط الأجهزة في DevTools) لاختبار كل تمرين. جرّب قبل كشف الحل.

تمرين 1 — وسم viewport ونقطة توقف أولى

تخطيط بعمودين يجب أن يطوى إلى عمود واحد تحت 700px. أدرج سطر الترميز الذي يجعل الهاتف يحترمه.

اعرض الحل
<meta name="viewport" content="width=device-width, initial-scale=1" />
.layout { display: grid; grid-template-columns: 1fr; gap: 16px; } /* الأساس: عمود واحد */
@media (min-width: 700px) {
  .layout { grid-template-columns: 1fr 1fr; } /* 700px وما فوق: عمودان */
}

mobile-first: العمود الواحد هو الافتراضي، والاستعلام يضيف العمود الثاني للشاشات الأوسع. وبدون وسم viewport لن يعمل الاستعلام أبدا على الهاتف.

تمرين 2 — عنوان سائل بلا استعلام واحد

اجعل h1 يتحجّم بسلاسة من 28px على الشاشات الصغيرة إلى 48px على الكبيرة، دون الخروج عن هذا النطاق.

اعرض الحل
h1 {
  font-size: clamp(1.75rem, 1.1rem + 3.2vw, 3rem); /* 28px ← 48px، سائل */
}

clamp() تحدّ الأرضية عند 1.75rem (28px)، وتنمو مع نافذة العرض عبر حد vw، وتحدّ السقف عند 3rem (48px) — مستبدلة عدة نقاط توقف بإعلان واحد.

تمرين 3 — حاوية متجاوبة بلا media queries

وسّط عمود محتوى يكون بكامل العرض (ناقص هامشي 2rem) على الشاشات الصغيرة لكنه لا يتجاوز 1100px على الكبيرة.

اعرض الحل
.container {
  width: min(100% - 2rem, 1100px);
  margin-inline: auto;
}

min() تختار الأصغر: 100% - 2rem السائل على الهواتف، أو سقف 1100px على أسطح المكتب. بلا حاجة لـ @media.

تمرين 4 — احترام تقليل الحركة

لديك زر بانتقال transform مدته 300ms. عطّل الحركة للمستخدمين الذين طلبوا تقليلها.

اعرض الحل
.btn { transition: transform 300ms ease; }

@media (prefers-reduced-motion: reduce) {
  .btn { transition: none; }
}

النمط الأساسي يبقي الحركة للبقية؛ واستعلام الميزة يزيلها فقط للمستخدمين الذين فعّلوا إعداد "تقليل الحركة" في النظام. أساس لإمكانية الوصول، لا إضافة.

تمرين 5 — استعلام حاوية لبطاقة قابلة لإعادة الاستخدام

.card يجب أن تكدّس صورتها فوق نصها افتراضيا، لكنها تتحول إلى صورة-بجانب-نص حين تكون حاويتها هي بعرض 380px على الأقل — بغض النظر عن عرض الصفحة.

<div class="card-host">
  <article class="card"><img /><div class="body">…</div></article>
</div>
اعرض الحل
.card-host { container-type: inline-size; }

.card { display: grid; grid-template-columns: 1fr; gap: 12px; } /* مكدّسة */

@container (min-width: 380px) {
  .card { grid-template-columns: 140px 1fr; } /* جنبا إلى جنب */
}

لأنه استعلام @container، تتكيّف البطاقة نفسها بشكل صحيح سواء جلست في شريط جانبي ضيق أو منطقة رئيسية واسعة — تجاوب حقيقي على مستوى المكوّن لا تستطيع media query لنافذة العرض التعبير عنه.

النموذج الذهني الذي تحتفظ به

ابدأ بوسم viewport، ثم صمّم بنهج mobile-first: أنماط بلا استعلام هي الشاشة الصغيرة، واستعلامات min-width تضيف بنية كلما ظهرت مساحة. ضع نقاط التوقف حيث ينكسر المحتوى، لا حيث يقع جهاز. فضّل الأدوات السائلة — clamp() وmin() و% وrem — لما يجب أن يتحجّم، واحجز media queries للتغييرات البنيوية الحقيقية. احترم prefers-reduced-motion وprefers-color-scheme، وأبقِ الصور سائلة بـ max-width: 100% وsrcset، والجأ إلى استعلامات الحاوية حين يجب أن يستجيب مكوّن لصندوقه هو لا للصفحة كلها. افعل ذلك، فتناسب قاعدة كود واحدة كل شاشة.