مقدمه

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

تغییر استایل پیش‌فرض عناصر

گفتیم که نقش CSS، تعیین ظاهر و استایل عناصر وب است. اما عناصر HTML از یک استایل پیش‌فرض برخوردارند که بدون دخالت برنامه‌نویس روی آنها اعمال می‌شود. مثلاً عناصر هدینگ h1 تا h6 به صورت ضخیم (bold) و با سایز بزرگتری نسبت به سایر متون نمایش داده می‌شوند یا آیتم‌های لیست‌ها به صورت بالت‌دار (bulleted) و لینک‌ها با رنگی متفاوت و به صورت زیرخط‌دار (underlined) نمایش داده می‌شوند. سؤال اینجاست که این استایل‌های پیش‌فرض از کجا می‌آیند؟ منبع این استایل‌ها، مرورگرها هستند. در واقع، هر مرورگر دارای یک استایل‌شیت داخلی است که استایل پیش‌فرض عناصر را تعیین می‌کند. با این حساب، ما با استفاده از CSS در واقع، استایل‌های پیش‌فرض را برای برخی عناصر تغییر می‌دهیم.

معرفی Normalize.css

این موضوع که مرورگرها تعیین‌کننده‌ی استایل‌های پیش‌فرض عناصر هستند، با یک نگرانی ویژه همراه است و آن تفاوت‌های احتمالی بین استایل‌شیت‌های داخلی مرورگرهای مختلف است. این جنس نگرانی‌ها و چالش‌ها پای ثابت زندگی برنامه‌نویسان و توسعه‌دهندگان وب هستند. البته در این مورد، چندان جای نگرانی نیست و استایل‌شیت‌های داخلی مرورگرهای مختلف تا حد بسیار زیادی مشابه و یکسان هستند. در مورد تفاوت‌های موجود هم در گذشته از روشی به نام reset stylesheets استفاده می‌شد که البته امروز یک ورژن مدرن‌تر با نام normalize.css دارد. normalize.css یک استایل‌شیت ویژه است که با تعیین تعدادی استایل مشخص، ناسازگاری‌های موجود بین استایل‌شیت‌های مرورگرها را از بین می‌برد و در واقع، باعث می‌شود که نقطه‌ی شروع برای همه‌ی مرورگرها یکسان باشد. کافیست این استایل‌شیت را به صفحات وب خود اضافه کنید. در صفحه‌ی گیت‌هاب مربوط به این پروژه، می‌توانید اطلاعات بیشتری در مورد آن به دست آورید.

پردازش کدهای CSS

منظور از یک سند (document) معمولاً یک فایل متنی است که با استفاده از یک زبان نشانه‌گذاری (markup) ساختاردهی شده است. معروف‌ترین زبان نشانه‌گذاری که می‌شناسیم، HTML است. زبان‌هایی مانند XML و SVG نمونه‌های دیگری از زبان‌های نشانه‌گذاری هستند که در وب استفاده می‌شوند؛ البته خود SVG هم یک زبان مبتنی بر XML است. ارائه‌ی یک سند به کاربر، به معنای تبدیل آن سند به فرمی است که برای کاربر قابل درک باشد. برای نمونه، مرورگرهای وب که مرسوم‌ترین مفسرهای HTML و CSS هستند، اسناد وب را به صورت بصری (visual) به کاربران ارائه می‌دهند. یک مرورگر، در حکم یک عامل، نماینده یا ایجنت از طرف کاربر است و لذا گاهی از اصطلاح user agent برای آن استفاده می‌شود.

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

گام اول: ساخت DOM

DOM یا Document Object Model یک ساختمان داده در مرورگرهاست که عناصر موجود در یک سند وب و ساختار و سلسله‌مراتب آنها را در قالب یک درخت از اشیاء (objects) در حافظه‌ی کامپیوتر نمایش می‌دهد. مرورگرها با خواندن و تفسیر کدهای HTML صفحه، درخت DOM را ایجاد می‌کنند. برای درک ساختار DOM، صفحه‌ی HTML زیر را در نظر بگیرید:

<html>
<body>
  <h1>Hello World</h1>
  <div>
    <h2>Subtitle</h2>
    <p>Hello World!</p>
  </div>
</body>
</html>

درخت DOM متناظر با این صفحه به صورت زیر است:

درخت DOM برای یک سند وب ساده
درخت DOM برای یک سند وب ساده

همانطور که می‌بینید، هر عنصر HTML و هر عبارت متنی حکم یک node یا گره را برای درخت DOM دارد.

گام دوم: ساخت CSSOM

CSSOM یا CSS Object Model ساختار درختی دیگری است که سلسله‌مراتب استایل‌های یک سند را نمایش می‌دهد. در واقع، رابطه‌ی CSSOM با CSS مثل رابطه‌ی DOM با HTML است. استایل‌های زیر را برای صفحه‌ی بالا در نظر بگیرید:

body{
  font-size: 16px;
}

h1{
  font-size: 1.5rem;
  color: red;
}

div{
  padding: 1rem;
}

div h2{
  font-size: 1.2rem;
  color: blue;
}

div p{
  font-size: 0.9rem;
  color: gray;
}

مرورگر با تفسیر کدهای CSS صفحه، درخت CSSOM زیر را تولید می‌کند:

درخت CSSOM برای یک سند وب ساده
درخت CSSOM برای سند قبل و استایل‌های بالا

گام سوم: تولید Render Tree

وقتی DOM و CSSOM کامل شد، از ترکیب آنها با یکدیگر درخت رندر یا Render Tree ساخته می‌شود. درخت رندر شامل همه‌ی اطلاعاتی است که مرورگر برای رندر صفحه به آنها نیاز دارد. برای ساخت درخت رندر، مرورگر باید محاسبه کند که کدام قاعده‌های CSS باید روی کدام عناصر DOM اعمال شوند. تصویر زیر، درخت رندر حاصل از ترکیب DOM و CSSOM بالا را نشان می‌دهد:

درخت رندر برای یک سند وب ساده
درخ رندر برای مثال بالا

گام چهارم: Layout و Paint

مرورگرها بعد از ایجاد درخت رندر، جاسازی عناصر در صفحه را شروع می‌کنند. در این مرحله که Layout نامیده می‌شود، مرورگرها از روی مقادیر استایل‌هایی مانند width، height، margin و padding برای هر عنصر، سایز و موقعیت مکانی آن عنصر در صفحه را تعیین می‌کنند. البته در این مرحله، هنوز چیزی در صفحه نمایش داده نمی‌شود.

بعد از تکمیل Layout، مرورگر کار نقاشی (painting) را با اعمال استایل‌هایی مثل color و font برای تعیین پیکسل‌های واقعی که باید در صفحه ترسیم شوند، شروع می‌کند. اعمال برخی از استایل‌ها مثل گرادیان‌ها (gradients) به زمان بیشتری برای ترسیم و نقاشی نیاز دارد و سرعت لود صفحه را کمتر می‌کند.

پشتیبانی مرورگرها از CSS

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

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

در ادامه، روش‌هایی را برای طراحی سایت با هدف حداکثر کردن میزان پشتیبانی، معرفی می‌کنیم. این روش‌ها عبارتند از: تدارک مکانیزم fallback، استفاده از پیشوندهای مرورگرها و بهره‌گیری از ابزار آنلاین caniuse.com.

تدارک مکانیزم Fallback

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

قبلاً هم گفتیم که وقتی موتور CSS در هنگام تفسیر کدها به کدی می‌رسد که در آن از یک عبارت ناشناخته یا نامعتبر استفاده شده، از آن چشم‌پوشی می‌کند و خطایی را نیز گزارش نمی‌کند. در مثال زیر، نام پراپرتی color اشتباه نوشته شده است. مرورگر پس از رسیدن به این خط کد، به دلیل مواجهه با پراپرتی ناشناخته‌ی colour این خط کد را نادیده گرفته و به سراغ خط بعدی می‌رود:


p{
  colour: red;
  text-align: right;
}

موضوع مهم دیگری که باید بدانیم، این است که موتور CSS کار تفسیر کدها را از بالا به پایین انجام می‌دهد. یعنی دستورات و استایل‌هایی که زودتر نوشته شده‌اند، زودتر اجرا می‌شوند. در نظر گرفتن این دو موضوع، فرایند تدارک یک مکانیزم fallback را ساده می‌کند.

فرض کنید قصد داریم از یک ویژگی جدید استفاده کنیم که از وضعیت پشتیبانی خوبی در مرورگرهای قدیمی‌تر برخوردار نیست. برای مثال، تابع hsl() که در آینده به طور رسمی معرفی خواهد شد، امکان تعیین رنگ مورد نظر را با استفاده از سه مؤلفه‌ی درجه‌ی رنگ روی چرخه‌ی رنگ (hue)، غلظت یا میزان اشباع‌شدگی رنگ (saturation) و میزان روشنایی (lightness) به ما می‌دهد؛ اما مرورگرهای قدیمی‌تر از این مقدارِ تابعی پشتیبانی نمی‌کنند. از طرفی نمی‌خواهیم مزیت تعیین رنگ با استفاده از این تابع را در مرورگرهای مدرن‌تری که از آن پشتیبانی می‌کنند، از دست بدهیم. در این وضعیت، با توجه به دو موضوع ذکر شده یعنی نادیده گرفته شدن پراپرتی‌ها و مقادیر ناشناخته و تفسیر کدها از بالا به پایین، می‌توانیم از یک استایل برای تعیین رنگ با استفاده از تابع قدیمی‌تر rgb() استفاده کنیم و بلافاصله بعد از آن، استایل دیگری بیاوریم که از تابع مدرن hsl() برای تعیین رنگ استفاده می‌کند:

p{
  color: rgb(255, 0, 0);
  color: hsl(2, 100%, 50%);
}

به این ترتیب، مرورگری که از تابع hsl() پشتیبانی کند، با توجه به اینکه استایل مربوط به این تابع بعد از استایل مربوط به تابع rgb() آمده است، رنگ تعیین شده با استفاده از تابع hsl() را اعمال می‌کند و مرورگری که از تابع hsl() پشتیبانی نکند، به سادگی استایل مربوط به آن را نادیده گرفته و رنگ تعیین‌شده با استفاده از تابع rgb() را اعمال می‌کند.

استفاده از پیشوندهای مرورگرها

علاوه بر پراپرتی‌های استانداردی که توسط W3C معرفی می‌شوند، اغلب مرورگرها با یک کتابخانه‌ی داخلی شامل پراپرتی‌های اضافی همراه هستند. در حقیقت، حتی بعضی از پراپرتی‌هایی که بعداً به بخشی از استاندارد CSS تبدیل شدند، ابتدا توسط مرورگرها و در قالب این کتابخانه‌های داخلی معرفی و ارائه شدند. علاوه بر این، برای نسخه‌های قدیمی‌تر مرورگرها، گاهی اوقات تنها راه پشتیبانی از یک پراپرتی خاص CSS، استفاده از همین کتابخانه‌های داخلی مرورگرهاست. این کتابخانه‌ها حکم پلاگین‌های مرورگرها را دارند و برای دسترسی به آنها باید از پیشوندهای مرورگرها موسوم به vendor prefixes برای پراپرتی‌ها استفاده کنیم. در واقع، هر مرورگر، پیشوند خاص خود را برای دسترسی به کتابخانه‌ی اختصاصی خود دارد.

پیشوند موتور رندر مرورگر(ها)
-khtml- KHTML Konqueror
-moz- Mozilla Firefox, Camino
-ms- Trident IE
-o- Presto Nintendo Wii Browser
-webkit- Webkit Chrome, Edge, Opera (newer versions)

پس، به طور خلاصه می‌توان گفت که مرورگرها دارای کتابخانه‌ای از پراپرتی‌های اضافی هستند که دسترسی به آنها از طریق پیشوندهایی که هر مرورگر برای خود تعریف کرده، امکان‌پذیر است. انگیزه‌ی اصلی مرورگرها از ارائه‌ی این پیشوندها این است که توسعه‌دهندگان بتوانند پراپرتی‌های آزمایشی را به شکل ایمن آزمایش کنند.

وقتی پراپرتی جدیدی توسط W3C ارا‌ئه می‌شود، ابتدا در فاز آزمایشی (experimental) قرار دارد و پس از مدتی یا به همان شکل و یا با تغییرات و اصلاحاتی به استاندارد افزوده می‌شود و یا اینکه از استاندارد کردن آن صرف‌نظر می‌شود. اگر هنگامی که یک پراپرتی در فاز آزمایشی قرار دارد، توسعه‌دهنده از آن استفاده کند، ممکن است با تغییر یا حذف احتمالی این پراپرتی، مشکلاتی برای نمایش وبسایت به وجود آید. پیشوندهای مرورگرها، توسعه‌دهندگان را قادر می‌کنند که بدون نگرانی‌هایی از این دست، پراپرتی‌های آزمایشی را اجرا و تست کنند.

علاوه بر این، مرورگرها از این پیشوندها برای ارائه‌ی پراپرتی‌های اختصاصی خودشان که در سایر مرورگرها پیاده‌سازی نشده و بخشی از استاندارد CSS نیستند، استفاده می‌کنند. استفاده از پیشوندهای مرورگرها گاهی تنها راهی است که می‌توانیم برای اطمینان از پشتیبانی مرورگرهای قدیمی از یک پراپرتی جدیدتر، به کار ببریم. برای مثال، اگر بخواهیم مرورگرهای قدیمی‌تر از یک پراپرتی مثل transition پشتیبانی کنند، باید به شکل زیر از پیشوندهای مرورگرها استفاده کنیم:

-webkit-transition: all 4s ease;
-moz-transition: all 4s ease;
-ms-transition: all 4s ease;
-o-transition: all 4s ease;
transition: all 4s ease;

البته با توجه به اینکه بسیاری از توسعه‌دهندگان، بدون توجه به ماهیت آزمایشی پیشوندهای مرورگرها، از آنها در فاز تولید (production phase) پروژه‌های خود استفاده می‌کنند، مرورگرها در حال توقف کار روی این پیشوندها هستند و در عوض، به سمت تکنولوژی‌های مدرن‌تری مانند feature flags گرایش پیدا کرده‌اند که یک مکانیزم ساده‌ی مبتنی بر پیکربندی را برای فعال یا غیرفعال کردن ویژگی‌های آزمایشی ارائه می‌دهد.

استفاده از ابزار CanIUse

ابزار آنلاین caniuse.com یک مرجع بسیار مفید وکارامد برای بررسی وضعیت پشتیبانی مرورگرهای مخلف از امکانات و ویژگی‌های HTML، CSS و JavaScript محسوب می‌شود. این وبسایت، دارای یک دیتابیس برای به‌روز نگه‌داشتن اطلاعات خود در مورد وضعیت پشتیبانی مرورگرهاست.