بهینه‌سازی‌های رندر کردن مرورگر برای توسعه‌دهی frontend
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 13 دقیقه

بهینه‌سازی‌های رندر کردن مرورگر برای توسعه‌دهی frontend

جدول محتوا:

  • مقدمه
  • چه چیزی به یک فریم وارد می‌شود؟
  • Lifecycle برنامه
  • مسیر رندر کردن مرورگر و بهینه‌سازی‌های متنوع
  • نتیجه گیری

مقدمه

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

برخی از مورد قبول‌ترین شیوه‌ها در توسعه‌دهی وب امروزی، شامل فشرده‌سازی تصویر، کاهش کد، bundle کردن کد (با استفاده از ابزاری مانند Webpack) و... می‌باشند. این شیوه‌ها همین حالا هم بر روی بهبود رضایت کاربر تاثیر گذاشته‌اند، اما وقتی که توسعه دهندگان قدم‌های تحتانی که رندر کردن وب‌اپلیکیشن‌ها به DOM را راهنمایی می‌کنند، درک کنند، حتی می‌توان بیشتر از این را به دست آورد.

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

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

چه چیزی به یک فریم وارد می‌شود؟

هر زمان که یک تغییر بصری در یک وب‌اپلیکیشن ایجاد می‌شود، این اتفاق در کامپیوتر می‌افتد: مرورگر یک فریم جدید برای کاربر بالا می‌آورد تا آن را دیده، و با آن در تعامل باشد. نرخی که این فریم‌ها بر روی آن ظاهر می‌شوند، بر حسب فریم بر ثانیه (FPS = Frame Per Second) اندازه‌گیری می‌شود. اگر مرورگر در ساخت و رندر کردن یک فریم دیر کند، fps پایین می‌آید و کاربر ممکن است متوجه یک لگ در برنامه شود.

در جهت ساخت وب‌‌اپلیکیشن‌هایی با کارایی بالا که با نرخ ۶۰ فریم بر ثانیه اجرا می‌شوند، توسعه دهنده باید محتویات یک فریم را درک کند. در اینجا تقسیم‌بندی‌ای (در ۵ مرحله)‌ از نحوه ساخت یک فریم را مشاهده می‌نمایید:

۱. مرورگر یک درخواست GET را به یک سرور کنترل از راه دور ارسال می‌کند.

۲. سرور با مقداری کد HTML و CSS پاسخ می‌دهد.

۳. HTML در DOM مرورگر Parse می‌شود.

۴. CSS در یک مدل آبجکت CSS (CSSOM)، parse شده، و با DOM ادغام می‌شود تا یک ساختار درختی جدید به نام Render tree را بسازد.

۵. Render tree لزوما شامل عناصری که بر روی صفحه نمایش داده شده، و یک فریم را تشکیل می‌دهند، می‌باشد.

نکته: قدم ۴ در Chrome dev tools تحت عنوان Recalculate Styles شناخته می‌شود.

Lifecycle برنامه

قبل از این که به مسیر رندر کردن مرورگر و بهینه‌سازی‌هایی که می‌توانند به آن اعمال شوند وارد شویم، باید درباره lifecycle برنامه یاد بگیریم؛ زیرا ما را قادر خواهد ساخت تا تصمیمات هوشمندانه‌ای در تعیین این که برنامه چه زمانی باید «کار سخت» را انجام دهد، بگیریم و از این رو یک تجربه کاربری نرم بسازیم و رضایت کاربر را تقویت کنیم.

Lifecycle برنامه به ۴ سکو تقسیم می‌شود:

۱. بارگذاری

۲. بی حرکتی

۳. پویانمایی (انیمیشن)

۴. پاسخ

بارگذاری

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

بی حرکتی

پس از این که برنامه بارگذاری شده است، معمولا بی حرکت شده، و منتظر کاربر می‌ماند تا با آن در تعامل باشد. بلوک بی حرکتی معمولا حدود ۵۰ میلی ثانیه بوده، و فرصت انجام کارهای سخت مانند بارگذاری دارایی‌هایی که یک کاربر ممکن است بعدا به آن‌ها دسترسی داشته باشد (تصاویر، ویدیوها، بخش کامنت‌ها) را به توسعه دهنده می‌دهد.

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

پویانمایی (انیمیشن)

وقتی که کاربر شروع به تعامل با برنامه می‌نماید، سکوی بی حرکتی به پایان رسیده است. برنامه باید به درستی و بدون هیچ گونه تاخیر قابل دیدن، در قبال تعاملات کاربر واکنش نشان دهد.

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

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

در واقعیت، این مقدار باید بین ۱۰ تا ۱۲ میلی ثانیه باشد. یک راه برای رسیدن به این هدف، این است که تمام محاسبات انیمیشن را پیشاپیش (در طی ۱۰۰ میلی ثانیه پس از این که با یک عنصر رابط کاربری، تعاملی برقرار شده است) انجام دهیم.

مسیر رندر کردن مرورگر و بهینه‌سازی‌های متنوع

مسیر رندر کردن مرورگر، این راه را می‌پیماید:

  • JavaScript
  • محاسبات Style
  • ساخت طرح
  • رنگ آمیزی پیکسل‌های صفحه
  • ترکیب‌بندی لایه

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

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

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

JavaScript

JavaScript توسعه دهندگان را قادر می‌سازد تا انیمیشن‌ها و تجربه بصری خوبی برای کاربران فراهم کنند و از این رو به مقدار زیادی در وب‌اپلیکیشن‌ها استفاده می‌شود. طبق بحث خود درباره lifecycle برنامه، می‌بینیم که مرورگر حدود ۱۰ تا ۱۲ میلی ثانیه وقت داشت تا هر فریم را رندر کند. برای سبک کردن بار JavaScript در مسیر رندر کردن، مهم است که تمام کد JavaScript را در اولین فرصت ممکن در هر فریم اجرا کنیم؛ زیرا این کد می‌تواند نواحی دیگر مسیر رندر کردن را فعال کند.

رسیدن به این هدف با استفاده از متد window.requesAnimationFrame() ممکن است. طبق گفته اسناد وب MDN:

«متد windows.requestAnimationFrame() به مرورگر می‌گوید که شما می‌خواهید یک انیمیشن را اجرا کرده، و از مرورگر درخواست کنید که یک تابع مشخص را قبل از رنگ‌آمیزی مجدد، برای بروزرسانی انیمیشن فراخوانی کند. این متد یک callback را به عنوان یک آرگومان می‌گیرد، تا قبل از قبل از رنگ‌آمیزی مجدد فراخوانی شود.»

اِی‌پی‌آی requestAnimationFrame() مرورگر را قادر می‌سازد تا JavaScript را در زمان مناسب بیاورد و هیچ فریمی را از دست ندهد. در اینجا مثالی از متد را در هنگام استفاده مشاهده می‌نمایید:

function doAnimation() {
    // مقداری جادوگری کد
    requestAnimationFrame(doAnimation); //schedule the next frame
}

requestAnimationFrame(doAnimation);

تب performance در Chrome dev tools، توسعه دهندگان را قادر می‌سازد تا وقتی که یک صفحه تحت استفاده قرار دارد، آن را ضبط کرده، و رابطی را نمایش دهد که نحوه اجرای JavaScript در وب‌اپلیکیشن را نشان می‌دهد.

با این که requestAnimationFrame یک ابزار بسیار مهم است، اما برخی کدهای JavaScript می‌توانند به شدت نسبت به منابع حساس باشند. وبسایت‌ها بر روی thread اصلی سیستم عامل‌ها اجرا می‌‌شوند و از این رو اسکریپت‌ها می‌توانند اجرای سکوهای دیگر مسیر رندر کردن را با تاخیر مواجه کنند. برای رفع این مشکل، ما می‌توانیم از web workerها استفاده کنیم.

Web workerها ما را قادر می‌سازند تا threadهای جدیدی را برای کدهای JavaScript که نسبت به منابع حساس هستند، ایجاد کنیم. طبق گفته اسناد وب MDN:

«Web Workerها چاره ساده‌ای برای محتویات وب هستند، تا بتوانند اسکریپت‌ها را در threadهای پس‌زمینه اجرا کنند. Worker thread می‌تواند وظایف را بدون تداخل با رابط کاربری اجرا کند. یک worker پس از این که ساخته شد، می‌تواند یک پیغام را به کد JavaScript که با ارسال پیغام به handler رویداد مشخص شده توسط کد ساخته شده است (و برعکس)، ارسال کند.»

برای استفاده از این ویژگی، باید یک فایل JavaScript‌ جداگانه بسازید که برنامه اصلی شما آن را به داخل یک web worker خواهد فرستاد.

محاسبه استایل

تغییرات استایل، یک بخش کلیدی از مسیر رندر کردن هر وب‌اپلیکیشنی هستند؛ زیرا تعداد تغییرات استایل مورد نیاز توسط یک عنصر، مستقیما متناسب با هزینه محاسبات استایل می‌باشد.

به علاوه تعداد تغییرات استایل، تطابق انتخاب کننده‌ها هم باید در لیست بهینه‌سازی‌های رندر کردن به عنوان یک فاکتور در نظر گرفته شود. تطابق انتخاب کننده، برابر با روند تعیین این که کدام استایل باید به هر عنصر DOM اعمال شود، می‌باشد.

برخی استایل‌های خاص ممکن است زمان بیشتری نسبت به باقی استایل‌ها در پردازش بگیرند و این مسئله وقتی که تعداد عناصر مورد تاثیر توسط یک یا چند استایل تغییر می‌کند، با اهمیت می‌شود. یک رویکرد مناسب برای رفع این مشکل، متدلوژی «مسدود کردن تغییر دهنده عنصر» (BEM) می‌باشد. این متدلوژی منافع خوبی برای کارایی، مانند تطابق کلاس را فراهم می‌کند، که سریع‌ترین انتخاب کننده برای تطابق با مرورگرهای مدرن می‌باشد.

ساخت طرح 

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

رنگ‌آمیزی پیکسل‌های صفحه

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

پروفایل رنگ‌آمیزی همانطور که در بالا نشان داده شده است، تشخیص این که کدام نواحی صفحه در حال رنگ‌آمیزی شدن بوده، و چه زمانی در حال رنگ‌آمیزی شدن می‌باشند را ساده می‌کند. پروفایل رنگ‌آمیزی می‌تواند با فشردن کلید escape پس از جهت‌یابی به Chrome dev tools و انتخاب تب Rendering، یافت شود.

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

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

ترکیب‌بندی لایه

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

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

برای رفع این مشکل، عناصر موجود باید در لایه‌های متفاوتی وجود داشته باشند. این هدف می‌تواند با استفاده از ویژگی will-change در CSS و تنظیم صفت آن برابر با transform تحقق یابد:

<element_to_promote> {

    will-change: transform;

}

گرچه بهتر است دقت کنید که افزایش لایه‌ها، یعنی افزایش زمان صرف شده بر روی مدیریت و ترکیب‌بندی لایه‌ها. با استفاده از Chrome dev tools، می‌توان به مانند زیر، تمام لایه‌ها را بر روی یک صفحه دید:

برای رفتن به تب Layers، بر روی منوی همبرگر در Chrome dev tools کلیک کنید، به بخش more tools‌ جهت‌یابی کنید و Layers را انتخاب کنید.

نتیجه گیری

ما مسیر رندر کردن مرورگر، lifecycle برنامه و بهینه‌سازی‌های متنوعی که می‌توانند به مسیر رندر کردن در هنگام lifecycle پویانمایی یک وب‌اپلیکیشن اعمال شوند را به صورت خلاصه دیده‌ایم.

وقتی که این بهینه‌سازی‌ها به صورت منطقی پیاده‌سازی شده‌اند، نتیجه نهایی یک تجربه کاربری و مقداری رضایت درونی برای توسعه دهندگان frontend می‌باشد.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید