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

فصل ۳ از ۸

پیشرفت ترم
۰٪

ترم ۴ · Responsive

تصویر responsive

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

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

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

یک عکسِ واحد که در سه جعبهٔ با عرض‌های مختلف قرار گرفته و در هر کدام بدونِ سرریز و بدونِ کش‌آمدن جا شده است

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

  • با max-width: 100% جلوی سرریزِ یک عکس را بگیری
  • با object-fit یک عکس را در یک شکلِ مشخص بدونِ کش‌آمدن جا بدهی
  • با aspect-ratio جای عکس را رزرو کنی تا صفحه نپرد
  • با srcset و sizes بگذاری مرورگر اندازهٔ درستِ عکس را انتخاب کند

قبل از شروع#

  • از ترم ۱: <img>، src، alt، width/height و <figure>/<figcaption> — پایهٔ کار همین‌هاست.
  • از فصل ۱ و ۲ (همین ترم): max-width، % و mobile-first.
  • از ترم ۳: display: flex، align-items و gap — برای چیدنِ عکسِ نویسنده کنارِ اسمش.

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

۱. تصویری که از جعبه‌اش بیرون می‌زند#

بیا با یک پستِ ساده شروع کنیم: یک <main> باریک با یک تیتر و یک عکسِ بزرگ. عکس را با <img> می‌گذاریم، مثل ترم ۱:

<main class="post">
  <h1>یک سفر کوتاه</h1>
  <img src="https://picsum.photos/id/1015/800/450"
       alt="رودخانه‌ای که از میان کوه‌های جنگلی می‌گذرد"
       width="800" height="450">
</main>

و به .post یک عرضِ نسبی بده تا مثل فصل ۱ جمع‌وجور بماند:

.post {
  max-width: 700px;
  margin: 0 auto;
}

حالا نگاه کن. .post نهایتاً ۷۰۰ پیکسل است، ولی عکس ۸۰۰ پیکسل پهناست — و از جعبه‌اش بیرون می‌زند، از لبهٔ صفحه رد می‌شود و یک نوارِ لغزشِ افقی می‌سازد. عرضِ پیش‌نمایش را باریک‌تر کن؛ بدتر می‌شود، چون عکس همچنان اصرار دارد ۸۰۰ پیکسل بماند.

🔧 اگر کار نکرد: بله، این سرریز همان چیزی است که باید ببینی. یک <img> به‌طور پیش‌فرض به عرضِ واقعیِ فایل کشیده می‌شود و به عرضِ جعبه‌اش کاری ندارد. علاجش یک خطِ کوتاه است که در بخش بعد می‌نویسیم: max-width: 100%.

۲. max-width: 100% و height: auto#

max-width: 100% روی یک عکس یعنی «هیچ‌وقت پهن‌تر از جعبه‌ات نشو». اگر جعبه از عکس بزرگ‌تر بود، عکس اندازهٔ واقعی‌اش می‌ماند؛ اگر جعبه کوچک‌تر شد، عکس با آن جمع می‌شود. height: auto را هم کنارش می‌گذاری تا وقتی عرض کم می‌شود، ارتفاع هم به همان نسبت کم شود و عکس کش نیاید:

.hero {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
}

چک کن: حالا عرضِ پیش‌نمایش را باریک و پهن کن. عکس دیگر بیرون نمی‌زند و نوارِ لغزشِ افقی رفته؛ روی صفحهٔ باریک با آن جمع می‌شود و نسبتش هم حفظ می‌ماند. این دو خط (max-width: 100% و height: auto) مهم‌ترین چیزِ کلِ فصل‌اند — تقریباً هر عکسی روی وب به همین دو نیاز دارد.

💡 نکته: فرق width: 100% و max-width: 100% مثل فصل ۱ است: width: 100% عکسِ کوچک را هم به زور بزرگ می‌کند تا کلِ جعبه را پر کند (و تارش می‌کند)، ولی max-width: 100% فقط جلوی بزرگ‌ترشدن از جعبه را می‌گیرد و عکسِ کوچک را کش نمی‌دهد. برای عکس، تقریباً همیشه max-width را می‌خواهی.

۳. object-fit: پر کردنِ یک شکل بدونِ کش‌آمدن#

گاهی می‌خواهی عکس یک شکلِ مشخص بگیرد — مثلاً عکسِ گردِ نویسنده که باید مربع (بعد گرد) باشد، هرچند فایلش مستطیل است. بیا <figure> نویسنده را اضافه کنیم:

<figure class="author">
  <img class="avatar"
       src="https://picsum.photos/id/64/300/200"
       alt="عکس نویسنده" width="300" height="200">
  <figcaption>نوشتهٔ آوا</figcaption>
</figure>

فایلِ این عکس مستطیل است (۳۰۰ در ۲۰۰). اگر به زور در یک مربعِ ۹۶ در ۹۶ بگذاری‌اش، پهن‌به‌پهن فشرده می‌شود و صورتِ آدم کشیده و بدشکل می‌شود:

.avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
}

این را در آزمایشگاه امتحان کن و ببین: عکس در مربع فشرده و لهیده شده. object-fit: cover این را حل می‌کند — به عکس می‌گوید «جعبه را پر کن، ولی نسبتت را نگه دار؛ هرچه اضافه آمد را از لبه‌ها ببُر». height: 96px را با object-fit عوض کن:

.avatar {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 50%;
}

چک کن: حالا عکسِ گرد بدونِ هیچ کش‌آمدنی جا شده؛ به‌جای فشرده‌شدن، وسطش نگه داشته شده و طرفینِ اضافه بریده شده. object-fit: cover رایج‌ترین حالت است؛ یک حالتِ دیگرش contain است که کلِ عکس را نشان می‌دهد و اگر لازم شد دورش فضای خالی می‌گذارد.

مقایسهٔ یک عکسِ منظرهٔ مستطیل که در یک جعبهٔ مربع قرار می‌گیرد: سمت راست فشرده و کش‌آمده، سمت چپ با object-fit وسطش نگه‌داشته و لبه‌ها بریده و بدونِ کش‌آمدن

۴. aspect-ratio: نسبتِ ثابت بدونِ پرش#

آن height: 96px یک عیبِ کوچک دارد: عددِ ثابت است. اگر بعداً عرض را عوض کنی، شکل به‌هم می‌ریزد. aspect-ratio بهتر است: به‌جای ارتفاعِ ثابت، نسبت را ثابت می‌کند و ارتفاع را خودش از روی عرض حساب می‌کند. برای یک مربع می‌نویسی aspect-ratio: 1 / 1:

.avatar {
  width: 96px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 50%;
}

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

💡 نکته: همان width و height که در ترم ۱ روی <img> گذاشتی هم همین کارِ رزروِ جا را می‌کند. aspect-ratio وقتی به کار می‌آید که شکلِ نمایشِ عکس با شکلِ فایلش فرق دارد.

۵. srcset و sizes: عکسِ درست برای هر عرض#

یک مشکل باقی مانده که دیده نمی‌شود ولی مهم است: عکسِ بزرگ همیشه فایلِ ۸۰۰ پیکسلی را دانلود می‌کند، حتی روی یک موبایلِ باریک که فقط ۳۶۰ پیکسل جا دارد. یعنی موبایل دارد دیتای هدر می‌دهد. srcset این را حل می‌کند: چند نسخه با عرض‌های مختلف به مرورگر می‌دهی و می‌گویی هر کدام چند پیکسل پهناست (با پسوندِ w)، و مرورگر خودش مناسب‌ترین را برمی‌دارد:

<img
  class="hero"
  src="https://picsum.photos/id/1015/800/450"
  srcset="https://picsum.photos/id/1015/400/225 400w,
          https://picsum.photos/id/1015/800/450 800w,
          https://picsum.photos/id/1015/1200/675 1200w"
  sizes="(min-width: 700px) 680px, 100vw"
  alt="رودخانه‌ای که از میان کوه‌های جنگلی می‌گذرد"
  width="800"
  height="450">

srcset فهرستِ نسخه‌هاست و 400w یعنی «این فایل ۴۰۰ پیکسل پهناست». sizes به مرورگر می‌گوید عکس روی صفحه چقدر جا می‌گیرد تا بتواند درست انتخاب کند: اینجا نوشته‌ایم «اگر عرضِ صفحه از ۷۰۰ بیشتر بود، عکس ۶۸۰ پیکسل جا می‌گیرد؛ وگرنه کلِ عرضِ صفحه». آن 100vw یعنی «کلِ عرضِ صفحه» — این vw را فعلاً همین‌طور بپذیر؛ فصل بعد کامل یادش می‌گیریم. src هم می‌ماند به‌عنوان نسخهٔ پیش‌فرض برای مرورگرهای خیلی قدیمی.

چک کن: روی صفحه فرقی نمی‌بینی — و همین درست است. سودِ srcset در سرعت و مصرفِ دیتاست، نه در ظاهر. مرورگر روی موبایل نسخهٔ کوچک را برمی‌دارد و روی دسکتاپِ پهن نسخهٔ بزرگ را.

۶. <picture>: وقتی عکسِ متفاوت می‌خواهی#

srcset نسخه‌های هم‌شکل با اندازه‌های مختلف می‌دهد. ولی گاهی می‌خواهی روی موبایل یک عکسِ متفاوت نشان بدهی — مثلاً یک بُرشِ نزدیک‌ترِ عمودی به‌جای منظرهٔ عریض. برای این کار <picture> هست: چند <source> با شرطِ media می‌گذاری و مرورگر اولین شرطِ درست را برمی‌دارد؛ <img> هم همیشه ته‌اش می‌ماند به‌عنوان پیش‌فرض:

<picture>
  <source media="(min-width: 700px)"
          srcset="https://picsum.photos/id/1015/1200/675">
  <img src="https://picsum.photos/id/1015/500/700"
       alt="رودخانه‌ای در میان کوه‌ها"
       width="500" height="700">
</picture>

اینجا روی صفحهٔ پهن عکسِ عریض می‌آید و روی باریک عکسِ عمودی. به این کار «art direction» می‌گویند: تصمیم می‌گیری روی هر صفحه کدام بُرش بهتر است. برای اکثرِ عکس‌ها همان srcset کافی است؛ <picture> را وقتی بیاور که واقعاً به عکسِ متفاوت نیاز داری.

🤖 با AI: یک <img> معمولی به دستیار AI ات بده و بپرس «این را برای responsive کامل کن — چه چیزهایی کم دارد؟». جوابش را خط‌به‌خط بسنج: آیا max-width: 100% را گفت؟ srcset و sizes را درست نوشت؟ alt را دست‌نخورده نگه داشت؟ بعد در آزمایشگاه امتحانش کن — AI گاهی sizes را غلط می‌نویسد و همان باعث می‌شود نسخهٔ اشتباه دانلود شود، که فقط با دیدنِ نتیجه پیدا می‌شود.

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

این هم دو فایلِ کامل، یک‌جا. اول 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>عکس responsive</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="post">
      <h1>یک سفر کوتاه</h1>
      <img
        class="hero"
        src="https://picsum.photos/id/1015/800/450"
        srcset="https://picsum.photos/id/1015/400/225 400w,
                https://picsum.photos/id/1015/800/450 800w,
                https://picsum.photos/id/1015/1200/675 1200w"
        sizes="(min-width: 700px) 680px, 100vw"
        alt="رودخانه‌ای که از میان کوه‌های جنگلی می‌گذرد"
        width="800"
        height="450">
      <p>این عکس روی هر عرضی اندازهٔ درستِ خودش را می‌گیرد و بدشکل نمی‌شود.</p>
      <figure class="author">
        <img
          class="avatar"
          src="https://picsum.photos/id/64/300/200"
          alt="عکس نویسنده"
          width="300"
          height="200">
        <figcaption>نوشتهٔ آوا</figcaption>
      </figure>
    </main>
  </body>
</html>

و styles.css:

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

body {
  background-color: #f6edda;
  margin: 0;
  padding: 16px;
}

.post {
  max-width: 700px;
  margin: 0 auto;
}

.hero {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
}

.author {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
}

.avatar {
  width: 96px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 50%;
}

چک کن: روی صفحه یک پستِ تمیز می‌بینی: عکسِ بزرگِ بالای مطلب که با عرض جمع و باز می‌شود، و عکسِ گردِ نویسنده کنارِ اسمش که شکلش را نگه می‌دارد. عرض را بکش و مطمئن شو هیچ‌چیز بیرون نمی‌زند و هیچ‌چیز کش نمی‌آید.

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

کلمه تلفظ به حروف فارسی یعنی چه
max-width: 100% مکس-ویدث صد درصد عکس هیچ‌وقت پهن‌تر از جعبه‌اش نمی‌شود
object-fit آبجکت-فیت چطور عکس داخلِ جعبه‌اش جا شود؛ cover یعنی پر کن و لبه‌ها را ببُر
aspect-ratio اسپکت-ریشو نسبتِ عرض به ارتفاع را ثابت می‌کند
srcset سورس-ست فهرستِ چند نسخهٔ عکس با عرض‌های مختلف برای انتخابِ مرورگر
sizes سایزِز به مرورگر می‌گوید عکس روی صفحه چقدر جا می‌گیرد
<picture> پیکچر برای دادنِ عکسِ متفاوت روی صفحه‌های مختلف (art direction)

تمرین‌ها

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

در فصل بعد#

الان چیدمان و تصویر، هر دو، responsive‌اند. یک چیزِ آخر مانده: متن. اندازهٔ فونت را هم می‌شود سیال کرد تا خودش با عرضِ صفحه بزرگ و کوچک شود — بدونِ اینکه برای هر breakpoint یک @media بنویسی. فصل بعد سراغِ ریاضیِ CSS می‌رویم: clamp()، min()، max()، calc() و همان واحدِ vw که این فصل قرضش گرفتیم، این‌بار کامل.

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

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