مقدمه

حرف C در CSS نماینده‌ی واژه‌ی Cascading است. واژه‌ی cascade در اغلب دیکشنری‌ها به عنوان مترادفی برای واژه‌ی waterfall یا آبشار تعریف می‌شود؛ اما در حقیقت، این دو واژه معانی متفاوتی دارند. واژه‌ی waterfall به جریان آب ایجاد شده در لبه‌ی صخره‌ها گفته می‌شود اما cascade به مجموعه‌ای از آبشارهای کوچک گفته می‌شود که در یک سراشیبی جریان دارند. در حقیقت، cascade یک ماهیت «سلسله‌مراتبی» دارد و این چیزی است که حرف C در CSS نمایندگی می‌کند. در ترمینولوژی وب، Cascade نام الگوریتمی است که برای اجرا در شرایط مشخصی تعریف شده و به ماهیت سلسله مراتبی CSS اشاره می‌کند. الگوریتم Cascade یکی از بنیادی‌ترین اصول زبان CSS است که درک آن برای توسعه‌دهندگان وب ضروری است. قوانین این الگوریتم، به تعیین اولویت اعمال استایل‌ها در زمان تعارض (conflict) بین قاعده‌ها مربوط می‌شوند. یک تعارض، زمانی رخ می‌دهد که دو یا چند قاعده، استایل‌های متفاوتی را برای یک عنصر تعریف کرده باشند؛ در این شرایط، این الگوریتم Cascade است که قاعده‌ی ارجح را انتخاب می‌کند. توسعه‌دهندگان وب، بدون داشتن درک مناسبی از الگوریتم Cascade و مکانیزم آن، به درک عمیقی از CSS نمی‌رسند و در پروژه‌های بزرگ با مشکلات زیادی روبرو خواهند شد. موضوع دیگری که به چگونگی اعمال استایل‌ها روی عناصر مربوط است، وراثت یا Inheritance است. بعضی پراپرتی‌های CSS مقدارشان را از عنصر والد به فرزند به ارث می‌رسانند و برخی دیگر خیر. در نظر نگرفتن این موضوع هم می‌تواند ما را با نتایج غیر قابل انتظار روبرو کند. مفاهیم Cascade و Inheritance به هم مرتبط هستند اما در عین حال، به بررسی جداگانه نیاز دارند. در این درس، با مفهوم Cascade و در درس بعد با Inheritance آشنا می‌شویم و سپس، با قرار دادن آنها در کنار یکدیگر، با روند اعمال استایل‌ها روی عناصر وب آشنا می‌شویم.

درک الگوریتم Cascade

همانطور که قبلاً هم گفتیم، CSS یک زبان rule-based است و هر rule یا قاعده از یک بخش انتخابگر و یک بخش شامل استایل‌ها تشکیل می‌شود. روند اعمال استایل‌ها به گونه‌ای است که باید در شرایط مختلف، اتفاق‌های مشخصی رخ دهد. برای مثال، اگر کلاس مشخصی به یک عنصر افزوده شود، مجموعه‌ای از استایل‌ها روی آن عنصر اعمال شود یا اگر عنصر x یک فرزند عنصر y باشد، استایل‌های مشخصی را دریافت کند. در نهایت، این مرورگر است که قاعده‌ها را دریافت کرده و تعیین می‌کند که کدام استایل باید در چه زمانی و روی چه عنصری اعمال شود.

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

در حقیقت، یک بخش کلیدی از توسعه‌ی CSS، نوشتن کدی است که نتایجش اصطلاحاً قابل انتظار (predictable) باشد. یکی از مصداق‌های «قابل انتظار» بودن کدها این است که در زمانی که برخورد یا تعارضی بین دو قاعده‌ی CSS رخ می‌دهد، فرایند مشخصی برای حل این تعارض وجود داشته باشد و Cascade الگوریتمی است که این فرایند را مشخص می‌کند.

نکته‌ی مهم برای درک الگوریتم Cascade این است که رفتار مرورگر با قاعده‌های CSS را به خوبی درک کنیم. البته یک قاعده به خودی خود هیچ ابهامی به همراه ندارد و مشکل به زمانی مربوط می‌شود که دو قاعده حاوی اطلاعات متعارضی در مورد استایل یک عنصر باشند. اگر سابقه‌ی کار با CSS را داشته باشید، شاید با مواردی مواجه شده باشید که استایل یک عنصر با آنچه انتظار دارید، متفاوت است و در واقع، استایل مورد نظرتان روی آن عنصر اعمال نمی‌شود؛ در این موارد، معمولاً علت این مشکل آن است که یک قاعده‌ی متعارض با قاعده‌ی فعلی وجود دارد که از سطح اولویت بالاتری برخوردار است. بدون درک الگوریتم Cascade نمی‌توانیم بفهمیم که مرورگر چگونه اولویت قاعده‌ها را تعیین می‌کند و لذا ممکن است نتیجه، مورد انتظار ما نباشد و ما را سردرگم کند.

اجازه دهید با ذکر یک مثال، ببینیم الگوریتم Cascade قرار است چه ابهاماتی را حل کند. مثال زیر را اجرا کنید.

 Copy Icon CSS
h1{
  color: red;
}

#page-title{
  color: green;
}

.title{
  color: blue;
}

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

اگر مثال بالا را اجرا کنید، خواهید دید که مرورگر در نهایت، رنگ سبز را برای عنصر h1 در نظر گرفته و به عبارت دیگر، بین این سه قاعده‌ی متعارض، قاعده‌ی دوم را ارجح تشخیص داده است. سؤال این است که مرورگر بر چه اساسی رنگ سبز را برای عنصر h1 در نظر می‌گیرد؟ در پاسخ باید گفت که مرورگر برای تعیین قاعده‌ی برنده‌ی این تعارض، گام‌های الگوریتم Cascade را دنبال می‌کند.

گام‌های الگوریتم Cascade برای حل تعارض بین قاعده‌ها به این صورت است:

  1. اگر یکی از قاعده‌های متعارض با استفاده از عبارت !important نشانه‌گذاری (mark) شده باشد، آن قاعده نسبت به سایر قاعده‌ها از اولویت بالاتری برخوردار است و الگوریتم همین‌جا به پایان می‌رسد؛ اما در غیر این صورت، به گام دوم می‌رویم.
  2. اگر مبدأ (origin) قاعده‌های متعارض (یعنی استایل‌شیتی که قاعده‌ها به آن تعلق دارند) با هم متفاوت باشد، قاعده‌ی متعلق به مبدأ دارای اولویت بالاتر، انتخاب می‌شود و الگوریتم به پایان می‌رسد؛ اما در صورت یکسان بودن مبدأ قاعده‌ها، به گام سوم می‌رویم.
  3. اگر یکی از قاعده‌های متعارض به صورت درون‌خطی (inline) اعمال شده باشد، آن قاعده به عنوان پیروز تعارض انتخاب شده و الگوریتم به پایان می‌رسد؛ اما در غیر این صورت، به گام چهارم می‌رویم.
  4. اگر انتخابگر یکی از قاعده‌های متعارض نسبت به انتخابگرهای سایر قاعده‌ها «خاص‌تر» باشد یا به عبارت دیگر، از specificity بالاتری برخوردار باشد، آن قاعده به عنوان برنده‌ی تعارض تعیین می‌شود و الگوریتم به انتها می‌رسد؛ اما در غیر این صورت (یعنی در صورت برابری specificity انتخابگرها)، به گام بعدی می‌رویم.
  5. از بین قاعده‌های متعارض، هر کدام که بعد از دیگری آمده باشد، قاعده‌ی دیگر را لغو (override) کرده و لذا اولویت اِعمال را دریافت خواهد کرد.

درک گام‌های پنج‌گانه‌ی الگوریتم Cascade برای کسانی که با CSS آشنایی قبلی ندارند، به توضیحات بیشتری نیاز دارد. برای مثال، باید بدانند که چه مبدأهایی برای یک قاعده وجود دارد و کدام مبدأ اولویت بالاتری دارد و یا اینکه باید نحوه‌ی محاسبه‌ی specificity یا همان میزان خاص‌بودن انتخابگرها را بدانند. در ادامه، برای آشنایی شما با جزئیات مربوط به گام‌های الگوریتم Cascade، این گام‌ها را یکی‌یکی بررسی می‌کنیم.

گام اول: عبارت !important

افزودن عبارت !important به انتهای مقدار یک پراپرتی، باعث می‌شود که قاعده‌ی مورد نظر از بالاترین اولویت ممکن برخوردار شود. در حقیقت، مرورگر هنگام مواجهه با قاعده‌ای که با عبارت !important نشانه‌گذاری شده، در هر تعارضی اولویت را به آن قاعده می‌دهد. مثال زیر را ببینید:

 Copy Icon CSS
h1{
  color: red !important;
}

#page-title{
  color: green;
}

.title{
  color: blue;
}

این همان مثالی است که در ابتدای این درس دیدیم؛ با این تفاوت که این بار عبارت !important را به انتهای مقدار پراپرتی color در قاعده‌ی اول افزوده‌ایم. اگر این صفحه را اجرا کنید، خواهید دید که این کار باعث شده عنصر h1 با رنگ قرمز نمایش داده شود؛ یعنی قاعده‌ای که با استفاده از عبارت !important مارک شده، به عنوان پیروز تعارض انتخاب شده است.

هشدا در مورد !important

استفاده از عبارت !important کار چندان مرسومی نیست و بهتر است حتی‌المقدور از آن اجتناب کنیم؛ چون وقتی استایلی را به صورت !important تعریف کردیم، دیگر راهی برای override کردن آن استایل در آینده وجود ندارد جز اینکه استایل جدید را نیز به صورت !important تعریف کنیم و آن را به همراه استایل قبلی به مرحله‌ی بعدی الگوریتم بفرستیم. وجود استایل‌های !important متعدد در صفحه، انعطاف‌پذیری کدها را کاهش می‌دهد.

گام دوم: مبدأ قاعده‌ها

ظاهر یک وبسایت، محصول یک استایل‌شیت واحد نیست؛ بلکه حاصل ترکیبی از چند استایل‌شیت است که با استایل‌شیت پیش‌فرض مرورگر شروع شده و (احتمالاً) با استایل‌های تعریف‌شده توسط کاربر (user-defined) ادامه می‌یابد و در نهایت، با استایل‌های برنامه‌نویس که به روش‌های خارجی (external)، داخلی (internal) و درون‌خطی (inline) تعریف می‌شوند، تکمیل می‌شود. به عبارت دیگر، مبدأ یک قاعده می‌تواند استایل‌شیت پیش‌فرض مرورگر، استایل‌شیت تعریف‌شده توسط کاربر و یا یک استایل‌شیت تعریف‌شده توسط برنامه‌نویس باشد. تصویر زیر، اولویت این چند مبدأ را نشان می‌دهد. همانطور که می‌بینید، استایل‌شیت مرورگر از پایین‌ترین اولویت و استایل‌شیت‌های برنامه‌نویس از بالاترین اولویت برخوردارند. در ضمن، در بین استایل‌شیت‌های برنامه‌نویس، استایل‌های inline اولویت بالاتری دارند و سطح اولویت استایل‌های خارجی و داخلی، یکسان است.

اما اجازه دهید در اینجا توضیحی در مورد هر یک از این مبدأها ارائه دهیم.

  • استایل‌های مرورگر (browser styles): این گروه از استایل‌ها که user agent styles نیز نامیده می‌شوند، توسط خود مرورگر و با این هدف تعریف شده‌اند که در غیاب سایر انواع استایل‌شیت‌ها، صفحه به شکل قابل قبولی برای کاربر نمایش داده شود. با وجودی که این استایل‌شیت‌ها برای مرورگرهای مختلف کاملاً یکسان نیستند اما عموماً کارهای مشابهی را انجام می‌دهند. مثلاً هدینگ‌ها و پاراگراف‌ها را با کمی حاشیه از بالا و پایین نمایش می‌دهند، سایز فونت هدینگ‌ها را بزرگتر می‌کنند، لینک‌ها را به صورت زیرخط‌دار (underlined) و لیست‌ها را به صورت بالت‌دار (bulleted) نمایش می‌دهند و کارهایی از این دست. موتور CSS مرورگر در اولین گام، این استایل‌های پیش‌فرض را روی عناصر صفحه اعمال می‌کند.
  • استایل‌های کاربر (user-defined styles): گروه بعدی، استایل‌هایی هستند که توسط کاربران نهایی یا همان بازدیدکنندگان سایت روی صفحه اعمال می‌شوند. این قابلیتی است که اکثر مرورگرها در اختیار کاربران قرار داده‌اند تا به عنوان مثال، کاربری با مشکل بینایی بتواند از فونت بزرگتر و کنتراست بیشتر برای مشاهده‌ی محتوای صفحه استفاده کند. این گروه از استایل‌ها نسبت به استایل‌های مرورگر، اولویت بالاتری دارند اما تعداد کاربران نهایی که این استایل‌ها را روی صفحه اعمال می‌کنند، زیاد نیست.
  • استایل‌های برنامه‌نویس (author styles): اینها همان استایل‌هایی هستند که برنامه‌نویس به یکی از روش‌های خارجی، داخلی و درون‌خطی یا ترکیبی از این روش‌ها ایجاد می‌کند. موتور CSS مرورگر پس از اعمال استایل‌های دو گروه قبل، این استایل‌ها را روی عناصر صفحه اعمال می‌کند. در بین سه روشی که برای تعریف استایل‌های برنامه‌نویس وجود دارد، استایل‌های درون‌خطی یعنی استایل‌هایی که با استفاده از صفت style مستقیماً روی عناصر اعمال می‌شوند، بالاترین اولویت را دارند و استایل‌های داخلی یعنی استایل‌هایی که درون عنصر style قرار می‌گیرند و استایل‌های خارجی که توسط عنصر link به صفحه لینک می‌شوند، از اولویت یکسانی برخوردارند.

بنابراین، الگوریتم Cascade چنانچه مسئله‌ی تعارض در گام اول حل نشود، در گام دوم، مبدأ استایل‌های متعارض را با هم مقایسه می‌کند؛ اگر مبدأ قاعده‌ها متفاوت باشد، بر اساس آنچه که گفته شد و در تصویر بالا هم دیده می‌شود، قاعده‌ی ارجح را مشخص می‌کند. اما چنانچه مبدأ قاعده‌های متعارض، یکسان باشد، به گام بعدی می‌رود.

گام سوم: استایل‌های Inline

در اغلب موارد، تعارض بین استایل‌های برنامه‌نویس رخ می‌دهد. به عبارت دیگر، استایل‌های متعارض در اغلب موارد، مبدأ یکسانی به نام author stylesheets دارند. یعنی یک برنامه‌نویس، استایلی را برای یک عنصر تعریف کرده و در جای دیگری از کدها خواسته یا ناخواسته استایل دیگری را برای آن عنصر تعریف می‌کند و تعارض شکل می‌گیرد.

یادآوری می‌کنم که استایل‌های برنامه‌نویس می‌توانند به روش خارجی (external)، داخلی (internal) و یا درون‌خطی (inline) تعریف شوند. استایل‌های inline یک تفاوت مشخص با استایل‌های داخلی و خارجی دارند و آن این است که نیازی به بخش انتخابگر ندارند؛ چون مستقیماً و با استفاده از صفت style عناصر روی آنها اعمال می‌شوند.

قاعده‌های متعارضی که در دو گام قبلی تعیین تکلیف نشده‌اند و به گام سوم رسیده‌اند، مبدأ یکسانی دارند (که این مبدأ معمولاً استایل‌های برنامه‌نویس است) و هیچ‌کدام به صورت !important تعریف نشده‌اند (یا همگی !important هستند). در این وضعیت، اگر یکی از قاعده‌های متعارض به صورت inline تعریف شده باشد، نسبت به سایر قاعده‌ها ارجح خواهد بود. مثال زیر را ببینید:

 Copy Icon HTML
<h1 id="page-title" class="title" style="color: yellow">Welcome</h1>

در اینجا چهار قاعده‌ی متعارض وجود دارد که هم‌مبدأ هستند و هیچ‌کدام !important نیستند. از آنجایی که یکی از این قاعده‌ها (که رنگ زرد را برای هدینگ تعریف کرده) به صورت inline تعریف شده، نسبت به سه قاعده‌ی دیگر (که به صورت internal تعریف شده‌اند)‌ دارای اولویت است و لذا رنگ زرد برای عنصر هدینگ در نظر گرفته می‌شود.

توجه داشته باشید که اگر بخواهیم استایلی را که به صورت inline ایجاد شده، توسط یک استایل جدید override کنیم، می‌توانیم استایل جدید را با استفاده از عبارت !important تعریف کنیم؛ اما برای لغو یک استایل inline که به صورت !important تعریف شده، هیچ راهی وجود ندارد. بهتر است که تا جای ممکن از به‌کارگیری استایل‌ها به روش inline اجتناب کنیم. در فصل اول هم گفتیم که این روش بیشتر برای اهداف آزمایشی مناسب است.

محاسبه Specifity انتخابگرها

اگر مبدأ قاعده‌های متعارض، یکسان باشد و هیچ‌یک از قاعده‌ها inline نباشند، عامل بعدی یعنی specificity قاعده‌ی ارجح را تعیین می‌کند. درک مفهوم specificity و نحوه‌ی محاسبه‌ی آن از اهمیت بالایی برخوردار است؛ چون در عمل، اکثر تعارضات در این گام حل و فصل می‌شوند.

گفتیم که واژه‌ی specificity به معنای میزان «خاص بودن» انتخابگر است و در مقابل واژه‌ی generality قرار دارد که به معنای میزان «عمومیت» است. انتخابگر id یک انتخابگر بسیار خاص و در واقع، خاص‌ترین انتخابگر است؛ زیرا تنها به یک عنصر اشاره می‌کند. به همین ترتیب، انتخابگر کلاس (class) نسبت به انتخابگر نوع (type) خاص‌تر محسوب می‌شود. البته تعیین specificity انتخابگرها به روش دقیق و سیستماتیکی نیاز دارد که بتواند در مورد مقایسه‌ی انتخابگرهای ترکیبی که شامل چندین نوع از انتخابگرها هستند، نیز راهگشا باشد. روش تعیین specificity سیستماتیک هست اما اصلاً پیچیده نیست. داستان از این قرار است که:

specificity هر انتخابگر یک عدد سه رقمی است که رقم اول یا صدگان برابر با تعداد انتخابگرهای id است، رقم دوم یا دهگان برابر با مجموع تعداد انتخابگرهای کلاس، شبه‌کلاس (pseudo class) و انتخابگرهای صفت است و رقم سوم یا یکان برابر با تعداد انتخابگرهای عنصر و شبه‌عنصر (pseudo element) است. ترکیب‌گرها و انتخابگر universal و نیز شبه‌کلاس :not هیچ نقشی در تعیین specificity ندارند و باید آنها را نادیده گرفت.

برای روشن‌تر شدن موضوع، چند مثال در قالب جدول زیر ارائه شده است.

Result Ones Tens Hundreds Selector
004 4 0 0 html body header h1{ }
013 3 1 0 body header.page-header h1{ }
020 0 2 0 .page-header .title{ }
100 0 0 1 #page-title{ }
201 1 0 2 #demo #test h2{ }
012 2 1 0 p.para>span{ }
011 1 1 0 div *.key{ }

حالا به مثال زیر توجه کنید.

 Copy Icon CSS
#test p{
  color: green;
}

.featured{
  color: red;
}

در اینجا قاعده‌ی اول باعث می‌شود تا هر سه پاراگراف با رنگ سبز نمایش داده شوند اما در ادامه، یک قاعده‌ی دیگر ایجاد شده تا یکی از این پاراگراف‌ها که دارای کلاس featured است، با رنگ قرمز نمایش داده شود. اما به دلیل specificity بالاتر قاعده‌ی اول، امکان اجرای قاهده‌ی دوم وجود ندارد و لذا همچنان هر سه پاراگراف با رنگ سبز نمایش داده می‌شوند.

اگر بخواهیم ترتیبی بدهیم که قاعده‌ی دوم، قاعده‌ی اول را override کرده و پاراگراف سوم با رنگ قرمز نمایش داده شود، می‌توانیم عبارت !important را به قاعده‌ی دوم اضافه کنیم. این روش کار می‌کند اما همانطور که قبلاً هم گفتیم، ایده‌ی خوبی نیست؛ چون تصور کنید چه اتفاقی می‌افتد اگر این کار را چند بار در کدهای خود انجام دهیم و یک صفحه‌ی مملو از عبارات !important ایجاد کنیم. بهتر است به جای اینکه با چنین روش‌هایی به دنبال دور زدن قوانین specificity باشیم، سعی کنیم کاری کنیم که این قوانین برای ما کار کنند. در مورد این مثال، می‌توانیم میزان specificity انتخابگر قاعده‌ی دوم را افزایش دهیم. مثلاً مانند زیر یک id به آن اضافه کنیم:

 Copy Icon CSS
#test p{
  color: green;
}

#test .featured{
  color: red;
}

این بار، specificity قاعده‌ی دوم از قاعده‌ی اول بالاتر است و لذا آن را override می‌کند. اما روش بهتری هم وجود دارد: بهتر است به جای بالا بردن specificity انتخابگر دوم، specificity انتخابگر اول را پایین بیاوریم. مثلاً می‌توانیم id را از انتخابگر اول حذف کنیم و به جای آن از یک انتخابگر عنصر (div) استفاده کنیم:

 Copy Icon CSS
div p{
  color: green;
}

.featured{
  color: red;
}

اساساً کار کردن با specificity ما را در پروژه‌های بزرگ، در شرایطی مشابه یک جنگ تسلیحاتی قرار می‌دهد. پس، بهتر است تا جایی که ممکن است specificity انتخابگرها را پایین نگه داریم تا وقتی به لغو قاعده‌ای نیاز داشتیم، دستمان باز باشد و به قول معروف: همه‌ی گزینه‌ها را روی میز داشته باشیم.

گام پنجم: ترکیب قاعده‌ها در کد منبع

در صورتی که مسئله‌ی قاعده‌های متعارض با توسل به specificity انتخابگرهای آنها نیز حل نشود، ترتیب قرارگیری قاعده‌ها در کد، قاعده‌ی ارجح را تعیین می‌کند؛ یعنی با توجه به اینکه مرورگر، کدهای صفحه را از بالا به پایین خوانده و تفسیر می‌کند، هر قاعده‌ای که بعد از دیگری آورده شود، به عنوان برنده‌ی تعارض انتخاب می‌شود. مثال زیر را ببینید:

 Copy Icon CSS
.test p{
  color: green;
}

div .featured{
  color: red;
}

نکته‌ای که باید به آن توجه داشته باشید این است که اگر یکی از دو قاعده‌ی متعارض به صورت internal و دیگری به صورت external آورده شده باشد، باز هم مشمول همین قانون هستند و بنابراین، اگر عنصر style (که شامل استایل‌های internal است) بعد از عنصر link (که حاوی استایل‌های external است) آورده شود، قاعده‌ی internal برنده‌ی تعارض خواهد بود و برعکس.

البته توسعه‌دهندگان وب در این مورد بر اساس یک قرارداد ضمنی، همیشه عنصر style را بعد از همه‌ی عناصر link قرار می‌دهند تا همیشه استایل‌های داخلی نسبت به استایل‌های خارجی در اولویت باشند. توصیه می‌کنم شما هم این قرارداد را رعایت کنید.