ارجحیت دستورات CSS

یکی از مواردی که در طول این چند سالی که کار طراحی انجام می‌دهم همه روزه با آن درگیر بودم و دانستنش رو از ملزومات کار طراحی می‌دانم٬‌ ارجحیت دستورات CSS یا همان Specificity است. شما هر پروژه ای انجام دهید حتی اگر آن پروژه بسیار کوچک هم که باشد باز هم ممکن است در بخشی از کار با ارجحیت دستورات مواجه شوید و داشتن درک درست از این ویژگی در CSS به شما کمک بسیاری می‌کند تا اشکالاتی که گاها به واسطه ارجحیت دستوات به وجود می‌آید را برطرف کنید.

CSS به شکلی طراحی شده است که در صورت وجود تناقض در کد شما و اعمال دو استایل مختلف به یک عنصر٬‌ دستوری که بر اساس یک اصول از پیش تعیین شده٬ قدرت بالاتری دارد به عنصر مورد نظر اعمال می‌شود. مثال ساده‌ای می‌زنم. فرض کنید در بخشی از فایل css خود از دستور font-size: 1.2em; استفاده کرده باشید٬ حال در بخش دیگری از کد دوباره به همان عنصر قبلی font-size: 1.4em; را اختصاص می‌دهید. اینجا باید مرورگر تصمیم بگیرد که دستور اول را به کد شما اعمال کند یا دستور دوم را. در این مقاله به طور مفصل در این باره بحث می‌کنیم.

دستورات با وزن یکسان داخل یک فایل CSS

مورد اول که گاهی از روش اشتباه و سهوا اتفاق می‌افتد این است که شما یک کلاس مشخص تعریف کرده و به یک عنصر اختصاص داده‌اید٬ حال فراموش می‌کنید و در بخشی دیگر از کد دوباره همان کلاس را می‌نویسید و استایل متفاوتی به آن می‌دهید. مثلا به این شکل

.container {
	font-size: 1.2em;
}

.
.
.
. 

.container {
	font-size: 1.4em;
}

در اینجا دستوری که پایین‌تر نوشته شده است اعمال خواهد شد. زیرا وزن دستورات دقیقا یکسان است و در CSS در این شرایطی دستوری که داخل فایل CSS٬ پایین‌تر نوشته شده باشد دارای قدرت بیشتری بوده و دستور بالایی را خنثی می‌کند. همین وضعیت می‌تواند داخل یک بلوک دستور CSS هم اتفاق بیافتد به این شکل:

.container {
	font-size: 1.2em;
	color: #222;
	width: 960px;
	margin: 0 auto;
	font-size: 1.4em;
	box-shadow: 0 0 2px 3px #eee;
}

در اینجا خط ۲ و ۶ یک دستور هستند که دوبار تکرار شده اند. موارد مشابه به این زمانی رخ می‌دهد که شما کد را نوشته‌اید و در شرایط خاص در حال ویرایش کد هستید و فراموش می‌کنید که سایز فونت را یکبار در بالای کد نوشته‌اید و آن را دوباره در زیر دستور می‌نویسید. در اینجا هم دستوری که پایین‌تر آمده قوی‌تر است و دستور بالایی را خنثی کرده و خودش اعمال می‌شود. بین دو حالتی که اینجا ذکر شد مورد اول شایع تر است و مورد دوم خیلی کم اتفاق می‌افتد.

دستورات با وزن یکسان داخل فایل‌های CSS مختلف

مورد دیگری که باید به آن توجه کنید دستورات مشابهی است که داخل فایل‌های CSS مختلف قرار می‌گیرد. فرض بگیرید که ما دو فایل CSS داریم که بدین شکل به صفحه متصل‌اشان کرده‌ایم:

<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/layout.css">

حال یک دستور CSS داریم که در فایل اول است که بدین شکل است

.important {
	color: red;
}

و دقیقا مشابه به همین دستور را داخل فایل CSS دوم هم داریم:

.important {
	color: orange;
}

حال در اینجا کدام دستور اعمال می‌شود؟ در این شرایط هم دستور که در فایل پایین‌تر آمده باشد اعمال خواهد شد. ما اول فایل main.css را به صفحه پیوست کرده‌ایم و بعد از آن فایل layout.css را به صفحه اضافه کرده‌ایم. به خاطر اینکه فایل layout.css بعد از فایل main.css آمده در نتیجه دستورات داخلش ارجحیت داشته و در مثال بالا که وزن دستورات یکسان بود٬ دستوری که در فایل پایین‌تر قرار داشت به صفحه اعمال می‌شود. در اینجا اگر ما ۵ فایل CSS داشتیم محتوای فایل پنجم ارزش بالاتری به نسبت ۴ فایل قبلی دارد و فایل چهارم هم ارزش بالاتری از سه فایل قبلی خود دارد. البته دقت داشته باشید که ما هنوز درباره وزن دستورات صحبت نکرده‌ایم و دستوراتی که نوشتیم هم وزن و کاملا یکسان بوده‌اند.

جدا از تگ link ما می توانیم با دستور @import هم یک فایل CSS را به صفحه اضافه کنیم. به عنوان مثال:

<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/layout.css">
<style>
	@import "/css/typography.css";
</style>

پیشنهاد شده که همیشه تگ style را در زیر تگ‌های link استفاده کنیم. دستور @import هم باید داخل تگ style قرار بگیرد و همیشه باید قبل از بقیه دستورات و در بالای تگ style باشد. در اینجا نیز ارجحیت با دستوراتی است که داخل فایلی CSS قرار دارد که با دستور import به صفحه اضافه شده باشند. دقت کنید که تگ style را اگر بالای link ها قرار دهید دیگر ارجحیت با فایل CSS ای که با import به صفحه اضافه شده نیست و آخرین فایل CSS که بعد از همه به صفحه اضافه شده است ارجحیت بالاتر خواهد داشت. البته استفاده از دستور @import برای اضافه کردن فایل CSS به صفحه به این شکل دیگر مرسوم نیست.

مورد بعدی هم زمانی است که شما در زیر همان دستور @import دستورات CSS دیگری بنویسید. بدین شکل:

<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/layout.css">
<style>
	@import "/css/typography.css";
	
	.important {
		color: blue;
	}
	
</style>

در اینجا ارجحیت با دستورات CSS ای است که که بعد از دستور @import به صفحه اضافه شده اند. باز هم دقت داشته باشید که تا اینجای کار وزن همه دستورات یکسان بوده فقط محل قرارگیری آنها بوده است که تفاوت داشته. از اینجا به بعد وزن دستورات را بررسی می‌کنیم.

دستورات با وزن‌های مختلف در یک فایل CSS

در مثال‌های قبلی تنها درباره دستوراتی صحبت کردیم که وزن یکسانی دارند و تنها محل قرارگیری‌شان متفاوت است. حال می‌خواهیم درباره دستورات با وزن‌های مختلف بحث کنیم. فرض کنید شما داخل صفحه خود ساختاری بدین شکل را دارید:

<div id="container">
  <p class="text">متن نمونه برای نشان دادن ارجحیت دستورات</p>
</div>

و برای کد بالا دستورات CSS ای بدین شکل نوشته‌اید:

#container {
  color: red;
}

.text {
  color: orange;
}

p {
  color: green;
}

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

See the Pen zBawC by amir-abbas abdolali (@amir-abbas) on CodePen.

حال ببینم چطور باید وزن هر دستور را حساب کنیم.

روش محاسبه وزن دستورات CSS

محاسبه وزن در CSS بر اساس یک مقدار چهار رقمی است. علامتی که فاقد وزن باشد با مقدار 0,0,0,0 نمایش داده می‌شود.

  1. کم ارزشترین علائم در CSS در محسابه وزن٬ گزینشگر عمومی یا همان universal selector است که با علامت * نمایش داده می‌شود و عملا وزنی ندارد. جدا از این گزینشگر٬‌ تمامی combinator ها در CSS نیز فاقد وزن هستند. لیست کامل بدین شکل است:
    *‌     /* 0,0,0,0 for universal selector and all css combinators */
    +
    ~
    >
  2. تمامی تگ‌های HTML و تمامی pseudo element ها دارای وزن 0,0,0,1 هستند. در مستندات CSS2 مشخص نبود که سودو المنت‌ها وزن دارند یا خیر ولی در نسخه CSS2.1 به طور مشخص تعریف شد که سودو المنت‌ها نیز دارای وزن هستند و وزنشان یک است. لیست تگ‌های HTML را در اینجا نمی آورم ولی لیست سودو المنت ها را درج می‌کنم:
    ::after        /* 0,0,0,1 for the all pseudo elements */
    ::before
    ::first-letter
    ::first-line
    ::selection
    سودو المنت‌ها را با دو علامت :: نمایش می‌دهند تا از سودو کلاس‌ها متمایز شوند. البته این قابلیت در مرورگر‌های جدیدتر پشتیبانی شده و در مرورگرهای قدیمی تر از همان یک علامت : پشیتبانی شده است.
  3. تمامی کلاس‌ها٬ گزینشگرهای خصیصه یا همان attribute selector ها و همینطور همه pseudo class دارای وزن 0,0,1,0 هستند. در ادامه لیستی از دو نمونه از گزینشگر‌های خصیصه و لیست کامل سودو کلاس‌ها را می‌آورم:
    .css-class          /* 0,0,1,0  */
    
    a[href$="http"]   /* 0,0,1,0  */
    div[dir="rtl"]       /* 0,0,1,0  */
    
    :active               /* 0,0,1,0  for the rest of pseudo classes */
    :checked
    :default
    :dir()
    :disabled
    :empty
    :enabled
    :first
    :first-child
    :first-of-type
    :fullscreen
    :focus
    :hover
    :indeterminate
    :in-range
    :invalid
    :lang()
    :last-child
    :last-of-type
    :left
    :link
    :not()   /* without weight */
    :nth-child()
    :nth-last-child()
    :nth-last-of-type()
    :nth-of-type()
    :only-child
    :only-of-type
    :optional
    :out-of-range
    :read-only
    :read-write
    :required
    :right
    :root
    :scope
    :target
    :valid
    :visited
    البته دقت کنید که :not() خودش وزنی ندارد ولی هر چیزی که داخل پرانتزش بیاید فقط وزن همان حساب خواهد شد
  4. تمامی ID ها در فایل CSS دارای وزن 0,1,0,0 هستند. البته پیشنهاد می‌شود به خاطر وزن بالای آیدی در CSS از آن استفاده نکنیم که در ادامه درباره این موضوع توضیح خواهم داد.
  5. پس از موارد قبلی استایل‌های درون خطی یا همان inline styles هستند که بالاترین وزن را دارند و عملا وزنشان معادل 1,0,0,0 خواهد بود. پیشنهاد شده از استایل درون خطی استفاده نشود زیرا از یک سو باعث کثیف شدن کد شما و مخلوط شدن لایه HTML با CSS می‌شود و از سوی دیگر وزنش به قدری بالاست که نمی‌توانید آن را از داخل فایل CSS بازنویسی کنید مگر اینکه از دستور important! استفاده کنید.
  6. مورد آخر هم دستور important! است که بیشترین وزن را دارد و اکیدا پیشنهاد می‌شود از آن استفاده نکنید و تنها در مواردی خیلی خاص که هیچ راه دیگری وجود ندارد از این دستور استفاده کنید. وزن این دستور معادل 1,0,0,0,0 است و بالاترین وزن ممکن را دارد. دستوری که important! داشته باشد با هیچ چیز قابل بازنویسی و خنثی سازی نیست مگر اینکه دستور دیگری بنویسید و به آن هم important! بدهید.

حالا چند مثال می‌آورم و درباره آنها و شیوه جمع زدن وزن دستورات توضیح می‌دهم:

div.container p {...}    /* 0,0,1,2  winner */
p.important {...}    /* 0,0,1,1 */

.container .content article blockquote {...}     /* 0,0,2,2 */
blockquote#pullquote {...}     /* 0,1,0,1 winner */

در مثال اول ما یک تگ div داریم که وزنش ۱ است و کلاسی به آن اختصاص داده شده با نام container که وزن آن نیز ۱۰ است و در ادامه یک تگ p آورده شده که وزن آن نیز ۱ است که مجموع اینها ۱۲ می‌شود و در ادامه‌ یک پاراگراف داریم که کلاس important به آن اختصاص داده شده است که وزن کلاس معادل ۱۰ و وزن خود پاراگراف معادل ۱ است در نتیجه مجموع آن ۱۱ شده که از دستور اول ضعیف تر است.

در مثال دوم ما دو کلاس container و content داریم و دو تگ article و blockquote که دو کلاس معادل ۲۰ و دو تگ معادل ۲ و مجموع معادل ۲۲ می‌شود ولی در ادامه ما یک تگ blockquote داریم و یک آیدی blockquote که وزنشان روی هم ۱۰۱ خواهد شد و به مراتب وزن بیشتری از دستور اول دارد و قطعا دستور دوم قوی‌تر بوده و اعمال خواهد شد. حالا چند مثال دیگر هم می‌آورم:

h1 + p {...} /* 0,0,0,2 */
*.aside {...} /* 0,0,1,0 */
html > body table tr[id="totals"] td ul > li {...} /* 0,0,1,7 */
li#answer {...} /* 0,1,0,1 */

زمانی که وزن دستورات مطرح می‌شود دیگر مکان قرار گرفتن دستورات مطرح نیست. دستور قوی‌تر اگر بالا تر از دستور ضعیف هم قرار بگیرد باز هم دستور قوی‌تر اعمال خواهد شد.

نکته مهم

مقادیر عددی که برای تگ‌ها و کلاس‌ و آیدی‌ها و بقیه عناصر CSS در نظر گرفته شده مقادیری فرضی است. مثلا شرایط زیر را در نظر بگیرید:

.container .content .main .article .post .head-post .text .important .center .green .bold  {...}  /* 0,1,1,0 */
#bold {...}  /* 0,1,0,0 */

در اینجا ما ۱۱ کلاس داریم که سلسه‌وار و در ادامه هم آمده اند و اگر بخواهیم وزن آن را حساب کنیم معادل ۱۱۰ خواهد شد و در ادامه یک آیدی داریم که وزنش معادل ۱۰۰ هست. اگر وزن را ملاک قرار دهیم دستور اول قوی‌تر است ولی اینطور نیست. سلسه مراتبی که در قالب ۶ مورد در بخش قبل توضیح داده شد به این شکل است که هر سطح در هیچ شرایطی نمی‌تواند سطح قوی‌تر از خود را خنثی کرده و آن را بازنویسی کند. در اینجا هم اگر شما ۱۰۰۰ کلاس هم در کنار هم قرار دهید نمی‌توانید یک آیدی را خنثی کنید. برای نمونه همیشه از مثال مدال‌های المپیک استفاده می‌کنم. در جدول بازی‌های المپیک داشتن یک طلا از هزاران نقره هم بیشتر است و کشوری که یک طلا داشته باشد در رتبه بالاتری از کشورهای دیگر که گاها ۱۰ مدال نقره دارند قرار می‌گیرد. در اینجا هم دقیقا شرایط بدین گونه است. البته چون اعداد را مضربی از ۱۰ در نظر گرفته‌اند خیلی بعید است که شما با شرایطی مواجه شوید که ۱۰ کلاس در کنار هم قرار بگیرد.

چه نکاتی را رعایت کنیم که با مشکلات specificity مواجه نشویم؟

بارها در طراحی با شرایطی مواجه می‌شوید که دستوری می‌نویسید ولی بعد از refresh کردن صفحه هیچ تغییری در صفحه نمی‌بینید. بسیاری از مواقع دلیل این عدم تغییر این است که در جایی دیگر از فایل CSS شما دستوری نوشته شده است که وزنی بیشتر از وزن دستور شما دارد و اجازه نمی‌دهد که دستور شما اعمال شود. برای اینکه با مشکلات ارجحیت دستورات مواجه نشوید پیشنهاد می‌کنیم این کارها را انجام دهید:

  1. هیچگاه از آیدی برای استایل دادن به یک عنصر در صفحه استفاده نکنید زیرا وزن بسیار زیادی دارد و برای خنثی کردنش مجبورید به دستور دیگرتان هم آیدی بدهید
  2. از !important استفاده نکنید زیرا وزن بسیار بالایی دارد و با هیچ چیز قابل خنثی سازی و بازنویسی نیست و تنها کاری که می‌توانید انجام دهید این است که یک دستور CSS دیگر با وزن بالاتر بنویسید و به آن هم !important بدهید
  3. تا جایی که امکان دارد دستورات را به شکل تو در تو ننویسید و در جایی که مجبورید دستورات را به شکل تو در تو بنویسید سعی کنید بیش از ۳ مرحله تو رفتگی نداشته باشید. مثلا اگر دستور .container ul.nav را نوشته‌اید ولی دستور .nav به تنهایی کار می‌کند بهتر است از دومی استفاده کنید و بی‌جهت وزن دستورات را بالا نبرید
  4. سعی کنید به جای نوشتن دستورات به شکل تو در تو از کلاس استفاده کنید. مثلا به جای نوشتن .container .content article بهتر است به عنوان مثال کلاسی به نام .post را تعریف کنید و آن را به تگ article اختصاص دهید. البته باید سعی کنید که در تعریف کلاس‌ها زیاده‌روی نکنید و تعداد کلاس‌ها رقم معقولی باشد.
  5. در صورتی که تیم شما در طرح از آیدی استفاده کرده بهتر است به جای به کار بردن خود آیدی از گزینشگر خصیصه استفاده کنید. مثلا به جای اینکه بنویسید #container {...} بنویسید [id="container"] {...} که دقیقا همان کار را انجام می‌دهد ولی در اینجا دستور اول وزن ۱۰۰ دارد ولی دستور دوم وزنش ۱۰ است و خیلی راحت‌تر می‌توانید با نوشتن یک دستور دیگه آن را خنثی کرده و بازنویسی کنید.
  6. در شرایطی که می‌خواهید وزن یک دستور را بالا ببرید لازم نیست که به آن دستور کلاس دیگر اضافه کنید و یا تگ‌هایی که در مسیر رسیدن به آن تگ وجود دارد را ذکر کنید. تنها کافیست که همان کلاس را دوباره تکرار کنید. مثلا فرض کنید که شما کلاس .content را دارید و می‌خواهید وزن آن را بالاتر ببرید. می‌توانید تگ‌ها و کلاس‌هایی که والد این تگ هستند را بنویسید مثلا به این شکل html body .contaner .content که با این کار وزن دستور قبل را از ۱۰ به ۲۲ ارتقا خواهید داد ولی می‌توانید به جای اینکار همان کلاس را بدون فاصله چند بار به دنبال هم بنویسید به این شکل .content.content.content که از این طریق وزن همان دستور قبل از ۱۰ به ۳۰ افزایش پیدا می‌کند و شما از هیچ کلاس دیگری استفاده نکرده‌اید.

آیا همیشه باید وزن دستورات را به شکل دستی حساب کنیم؟

احتیاجی به این کار نیست. مطالب این مقاله تنها برای این بود که با مفهوم ارجحیت دستورات آشنا شوید. در حال حاضر chrome inspector و firebug و همینطور بقیه inspector های موجود برای دیگر مرورگرها به شکل اتوماتیک وزن هر دستور را برای شما حساب می‌کنند و دستوراتی که وزن بیشتر داشته باشند را بالاتر می‌نویسند. مثلا به این تصویر دقت کنید:

کروم اینسپکتور

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

چطور متوجه شویم که مشکل موجود از ارجحیت دستورات CSS است یا خیر؟

ممکن است دستوری را نوشته باشید ولی به عنصر مورد نظر شما اعمال نمی‌شود. راحت‌ترین راه برای فهمیدن اینکه مشکل از ارجحیت دستورات است یا نه این است که در آخر دستور از !important استفاده کنید و به طور موقت وزن دستور را بالا ببرید اگر تغییر مورد نظر شما اعمال شود متوجه می‌شوید که یک جای دیگر و در یک قسمت دیگر از فایل CSS دستوری نوشته‌اید که وزن بیشتری دارد و اجازه نمی‌دهد دستور شما اعمال شود. البته همانطور که گفتم Inspector هم وزن دستورات را نشان می‌دهد و اگر به آن هم دقت کنید متوجه مشکل خواهید شد.