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

فصل ۴ از ۸

پیشرفت ترم
۰٪

ترم ۴ · Responsive

تایپوگرافی سیال و ریاضی در CSS

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

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

آخر این فصل یک تیترِ بزرگ داری که سیال است: با عرضِ صفحه نرم بزرگ و کوچک می‌شود، ولی نه آن‌قدر بزرگ که روی دسکتاپ غول‌پیکر شود و نه آن‌قدر کوچک که روی موبایل خوانده نشود. و همهٔ این‌ها بدونِ حتی یک @media. رازش یک تکه ریاضیِ کوچک در خودِ CSS است: واحدهایی که به اندازهٔ صفحه بند‌اند، و توابعی که یک مقدار را بینِ یک کف و یک سقف نگه می‌دارند.

یک تیتر که با عرضِ صفحه بزرگ و کوچک می‌شود، ولی بینِ یک اندازهٔ کمینه و یک اندازهٔ بیشینه محدود مانده، مثلِ یک لغزنده که به دو گیرهٔ کف و سقف بسته شده است

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

  • با واحدهای vw و vh اندازه‌ها را به عرض و ارتفاعِ صفحه ببندی
  • بگویی چرا vw خالص در دو سرِ طیف خراب می‌شود
  • با clamp() یک اندازه را بینِ یک کف و یک سقف نگه داری
  • با min()، max() و calc() در CSS حساب کنی

قبل از شروع#

  • از ترم ۲: واحدهای rem و % و font-size — پایهٔ کار همین‌هاست.
  • از فصل ۱ و ۲ (همین ترم): max-width، mobile-first و @media — این فصل جای خیلی از @media ها را با ریاضی پر می‌کند.
  • از فصل ۳ (همین ترم): واحدِ vw را آنجا قرض گرفتیم؛ اینجا کاملش می‌کنیم.

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

۱. vw و vh: واحدی که به اندازهٔ صفحه بند است#

تا اینجا اندازه‌ها را با px و rem می‌دادی — واحدهایی که به صفحه کاری ندارند. vw فرق دارد: 1vw یعنی «یک درصدِ عرضِ صفحه». پس 100vw یعنی کلِ عرض، و 5vw یعنی پنج درصدِ عرض. چون به عرضِ صفحه بند است، وقتی صفحه پهن‌تر شود این اندازه هم بزرگ‌تر می‌شود — خودبه‌خود. یک خواهرِ هم دارد: vh که همان است ولی برای ارتفاع؛ 1vh یعنی یک درصدِ ارتفاعِ صفحه.

بیا روی تیتر امتحانش کنیم. در آزمایشگاه به h1 این را بده:

h1 {
  font-size: 5vw;
}

چک کن: عرضِ پیش‌نمایش را آرام پهن و باریک کن. اندازهٔ تیتر نرم با عرض بالا و پایین می‌رود — بدونِ هیچ @media، بدونِ هیچ جهش. این همان «سیال» بودن است: به‌جای چند اندازهٔ پله‌ای، یک تغییرِ پیوسته.

vh هم دقیقاً همین است، فقط برای ارتفاع. یک کاربردِ رایجش وقتی است که می‌خواهی یک ناحیه بلندیِ صفحه را پر کند — مثلِ یک بخشِ خوش‌آمدِ بالای سایت که می‌خواهی تقریباً کلِ صفحه را بگیرد. به .hero این را بده و ببین:

.hero {
  min-height: 80vh;
}

min-height: 80vh یعنی «حداقل هشتاد درصدِ ارتفاعِ صفحه بلند باش». ارتفاعِ پیش‌نمایش را کم و زیاد کن و ببین ناحیه با آن بالا و پایین می‌رود. برای ادامهٔ فصل این خط را می‌توانی برداری؛ تمرکزِ ما روی اندازهٔ فونت است، ولی خوب است بدانی vh هم همان‌جا هست.

۲. مشکل: vw خالص در دو سرِ طیف خراب می‌شود#

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

🔧 اگر کار نکرد: بله، این بزرگ‌شدنِ افسارگسیخته و کوچک‌شدنِ بیش‌ازحد همان چیزی است که باید ببینی — و مشکلِ اصلیِ vw خالص است. vw یک کف و یک سقف لازم دارد تا از حد خارج نشود. راهش این نیست که برای هر عرض یک @media بنویسی؛ راهِ تمیز یک تابعِ CSS است به اسم clamp() که در بخش بعد می‌سازیم.

۳. clamp(): یک مقدار با کف، دلخواه و سقف#

clamp() سه مقدار می‌گیرد و همیشه یکی را برمی‌گرداند که در بازه بماند: clamp(کف, دلخواه, سقف). کارش ساده است: مقدارِ دلخواه را حساب می‌کند؛ اگر از کف کمتر شد، کف را می‌دهد؛ اگر از سقف بیشتر شد، سقف را می‌دهد؛ وگرنه همان دلخواه. مقدارِ دلخواه را همان 5vw می‌گذاریم تا سیال بماند، ولی این‌بار بینِ دو مرز:

h1 {
  font-size: clamp(1.75rem, 5vw, 3.5rem);
  line-height: 1.2;
}

چک کن: حالا همان دو سرِ طیف را دوباره امتحان کن. روی صفحهٔ خیلی پهن، تیتر به 3.5rem می‌رسد و می‌ایستد — دیگر غول‌پیکر نمی‌شود. روی صفحهٔ خیلی باریک، زیرِ 1.75rem نمی‌رود و خوانا می‌ماند. بینِ این دو، هنوز نرم و سیال با 5vw تغییر می‌کند. یک خط، جای ده‌ها @media را گرفت.

یک محورِ افقی که مقدارِ دلخواه با شیب بالا می‌رود، ولی زیرِ یک خطِ کف و بالای یک خطِ سقف صاف می‌شود؛ بخشِ میانیِ شیب‌دار جایی است که clamp سیال است و دو سرِ صافش جایی که به کف و سقف قفل شده

💡 نکته: ترتیبِ سه مقدار را قاطی نکن: اول کف، وسط دلخواه، آخر سقف. یک قاعدهٔ سرانگشتی برای فونت: کف را روی کوچک‌ترین اندازه‌ای بگذار که هنوز راحت خوانده می‌شود، و سقف را جایی که دیگر بزرگ‌تر شدن زشت است. همین را برای <p> هم می‌شود کرد، فقط با عددهای کوچک‌تر.

قبل از ادامه، جواب بده: در clamp(1rem, 2vw, 1.5rem)، اگر 2vw روی یک صفحهٔ خیلی پهن برابرِ 4rem شود، خروجی چه می‌شود؟ (1.5rem — چون از سقف بیشتر شده، سقف برمی‌گردد.)

۴. min() و max(): کوچک‌ترین یا بزرگ‌ترین را انتخاب کن#

clamp() دو خواهرِ ساده‌تر دارد. min() کوچک‌ترینِ چند مقدار را برمی‌گرداند و max() بزرگ‌ترین را. اسمشان اول گیج‌کننده است، پس با یک مثالِ ملموس: برای عرضِ یک ناحیه بنویس min(90%, 800px). این یعنی «کوچک‌ترینِ این دو را بگیر» — روی صفحهٔ باریک 90% کوچک‌تر است پس ناحیه ۹۰٪ می‌شود (با کمی فاصله از لبه)، و روی صفحهٔ پهن 800px کوچک‌تر است پس ناحیه سرِ ۸۰۰ می‌ایستد. یعنی دقیقاً همان کارِ max-width را با یک خط انجام می‌دهد. به .hero بده:

.hero {
  width: min(90%, 800px);
  margin: 0 auto;
}

چک کن: عرض را بکش. ناحیه تا ۸۰۰ پیکسل بزرگ می‌شود و می‌ایستد، و روی باریک ۹۰٪ عرض را می‌گیرد و همیشه کمی حاشیه از لبه دارد. max() هم برعکس است: max(50%, 300px) یعنی «هیچ‌وقت باریک‌تر از ۳۰۰ پیکسل نشو» — یک کفِ اندازه.

۵. calc(): ریاضی و ترکیبِ واحدها#

calc() می‌گذارد داخلِ CSS حساب کنی، و مهم‌تر: واحدهای مختلف را با هم قاطی کنی — چیزی که وگرنه ممکن نیست. مثلاً «کلِ عرض، منهای دو رِم فاصله» می‌شود calc(100% - 2rem). مرورگر 100% را که نمی‌داند چند پیکسل است و 2rem را با هم حساب می‌کند:

.hero {
  width: calc(100% - 2rem);
}

مهم: دورِ علامتِ منها و جمع فاصله بگذار (100% - 2rem، نه 100%-2rem)، وگرنه CSS آن را نمی‌فهمد. calc() با clamp() هم ترکیب می‌شود؛ مثلاً به‌جای یک 5vw خالی، می‌توانی مقدارِ دلخواه را نرم‌تر کنی: clamp(1rem, calc(0.5rem + 3vw), 3rem) — یک کفِ ثابتِ 0.5rem که 3vw رویش سوار می‌شود. فعلاً همین calc(100% - 2rem) را بفهم؛ ترکیب‌های پیشرفته‌ترش را در ترم‌های بعد بیشتر می‌بینی.

🤖 با AI: از دستیار AI ات بپرس «یک font-size سیال با clamp() برای یک تیتر بنویس که روی موبایل حدود ۲۸ پیکسل و روی دسکتاپ حدود ۵۶ پیکسل باشد». جوابش را خط‌به‌خط بخوان: کف و سقف را درست گذاشته؟ مقدارِ وسط vw دارد؟ بعد در آزمایشگاه بگذارش و دو سرِ طیف را بکش — AI گاهی کف و سقف را جابه‌جا می‌نویسد و آن‌وقت clamp() قفل می‌شود و اصلاً تغییر نمی‌کند. فقط با دیدن می‌فهمی‌اش.

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

این هم دو فایلِ کامل، یک‌جا. اول 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>تایپوگرافی سیال</title>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <main class="hero">
      <h1>سلام دنیا</h1>
      <p>این متن با عرضِ صفحه بزرگ و کوچک می‌شود، ولی هیچ‌وقت از حد خارج نمی‌شود.</p>
    </main>
  </body>
</html>

و styles.css — دقت کن که min() جای max-width را گرفته و clamp() اندازهٔ فونت را سیال کرده، همه بدونِ یک @media:

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

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

.hero {
  width: min(90%, 800px);
  margin: 0 auto;
  padding: clamp(1rem, 5vw, 3rem);
}

h1 {
  font-size: clamp(1.75rem, 5vw, 3.5rem);
  line-height: 1.2;
}

p {
  font-size: clamp(1rem, 2.5vw, 1.25rem);
  line-height: 1.6;
}

چک کن: روی صفحه یک تیتر و یک پاراگراف در یک ناحیهٔ وسط‌چین می‌بینی. عرض را از خیلی باریک تا خیلی پهن بکش: همه‌چیز نرم بزرگ و کوچک می‌شود، ولی نه تیتر غول‌پیکر می‌شود و نه متن ناخوانا. حتی padding هم با clamp() سیال است. این کلِ قدرتِ ریاضیِ CSS است.

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

کلمه تلفظ به حروف فارسی یعنی چه
vw وی-دبلیو یک درصدِ عرضِ صفحه؛ اندازه را به عرضِ صفحه می‌بندد
vh وی-اِیچ یک درصدِ ارتفاعِ صفحه
clamp() کلَمپ یک مقدار را بینِ یک کف و یک سقف نگه می‌دارد: clamp(کف, دلخواه, سقف)
min() مین کوچک‌ترینِ چند مقدار را برمی‌گرداند
max() مکس بزرگ‌ترینِ چند مقدار را برمی‌گرداند
calc() کَلک داخلِ CSS حساب می‌کند و واحدهای مختلف را قاطی می‌کند

تمرین‌ها

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

در فصل بعد#

الان چیدمان، تصویر و متن — هر سه — می‌توانند responsive باشند، و خیلی از کارها را بدونِ @media با ریاضی انجام می‌دهی. تا اینجا همه‌چیز به عرضِ کلِ صفحه نگاه می‌کرد. ولی گاهی یک component باید به عرضِ جعبهٔ خودش پاسخ بدهد، نه کلِ صفحه — مثلِ یک کارت که در یک ستونِ باریک یک‌شکل و در یک ستونِ پهن شکلِ دیگری می‌گیرد. فصل بعد سراغِ container query می‌رویم: media query، ولی این‌بار نه برای صفحه، بلکه برای هر جعبه جدا.

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

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