1. مقدمه
یک نسخه نمایشی و کدهای تعاملی برای یادگیری در مورد Interaction to Next Paint (INP) .
پیش نیازها
- آشنایی با توسعه HTML و جاوا اسکریپت.
- توصیه می شود: اسناد INP را بخوانید.
چیزی که یاد می گیرید
- نحوه تأثیر متقابل تعاملات کاربر و مدیریت شما از این تعاملات بر پاسخگویی صفحه.
- نحوه کاهش و حذف تاخیرها برای تجربه کاربری روان.
آنچه شما نیاز دارید
- رایانه ای با قابلیت شبیه سازی کد از GitHub و اجرای دستورات npm.
- یک ویرایشگر متن
- نسخه اخیر Chrome برای کارکرد همه اندازهگیریهای تعامل.
2. راه اندازی شوید
کد را دریافت و اجرا کنید
کد در مخزن web-vitals-codelabs
یافت می شود.
- مخزن را در ترمینال خود کلون کنید:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
- عبور از دایرکتوری شبیه سازی شده:
cd web-vitals-codelabs/understanding-inp
- وابستگی ها را نصب کنید:
npm ci
- سرور وب را راه اندازی کنید:
npm run start
- در مرورگر خود از http://localhost:5173/understanding-inp/ دیدن کنید
نمای کلی برنامه
در بالای صفحه یک دکمه شمارنده امتیاز و افزایش وجود دارد. یک نسخه نمایشی کلاسیک از واکنش پذیری و پاسخگویی!
در زیر دکمه چهار اندازه گیری وجود دارد:
- INP: امتیاز INP فعلی، که معمولاً بدترین تعامل است.
- تعامل: امتیاز آخرین تعامل.
- FPS: فریم های رشته اصلی صفحه در هر ثانیه.
- تایمر: یک انیمیشن تایمر در حال اجرا برای کمک به تجسم jank.
ورودیهای FPS و Timer اصلاً برای اندازهگیری تعاملات ضروری نیستند. آنها فقط برای تجسم پاسخگویی کمی ساده تر اضافه شده اند.
آن را امتحان کنید
سعی کنید با دکمه افزایش تعامل داشته باشید و شاهد افزایش امتیاز باشید. آیا مقادیر INP و Interaction با هر افزایش تغییر می کند؟
INP اندازه گیری می کند که از لحظه تعامل کاربر تا زمانی که صفحه واقعاً به روز رسانی ارائه شده را به کاربر نشان دهد چقدر طول می کشد.
3. سنجش تعامل با Chrome DevTools
DevTools را از منوی More Tools > Developer Tools با کلیک راست روی صفحه و انتخاب Inspect یا با استفاده از میانبر صفحه کلید باز کنید.
به پانل Performance بروید، که از آن برای اندازه گیری تعاملات استفاده خواهید کرد.
سپس، یک تعامل را در پانل عملکرد ثبت کنید.
- ضبط را فشار دهید.
- تعامل با صفحه (دکمه Increment را فشار دهید).
- ضبط را متوقف کنید.
در جدول زمانی حاصل، یک مسیر تعامل را خواهید یافت. با کلیک بر روی مثلث سمت چپ آن را بزرگ کنید.
دو تعامل ظاهر می شود. با اسکرول کردن یا نگه داشتن کلید W روی مورد دوم بزرگنمایی کنید.
با نگه داشتن ماوس روی تعامل، میتوانید مشاهده کنید که تعامل سریع بوده، زمان پردازش را صرف نمیکند، و حداقل زمان را در تاخیر ورودی و تاخیر ارائه ، که مدت زمان دقیق آن به سرعت دستگاه شما بستگی دارد.
4. شنوندگان رویدادهای طولانی مدت
فایل index.js
را باز کنید و تابع blockFor
را در شنونده رویداد حذف کنید.
مشاهده کد کامل: click_block.html
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
});
فایل را ذخیره کنید. سرور تغییرات را می بیند و صفحه را برای شما بازخوانی می کند.
دوباره سعی کنید با صفحه تعامل داشته باشید. اکنون تعاملات به طور قابل توجهی کندتر خواهد شد.
ردیابی عملکرد
ضبط دیگری را در پانل عملکرد بگیرید تا ببینید در آنجا به چه شکل است.
آنچه زمانی یک تعامل کوتاه بود، اکنون یک ثانیه کامل طول می کشد.
هنگامی که ماوس را روی تعامل قرار میدهید، متوجه میشوید که زمان تقریباً به طور کامل در «مدت پردازش» سپری میشود، که مقدار زمانی است که برای اجرای تماسهای شنونده رویداد صرف میشود. از آنجایی که تماس مسدودکننده blockFor
کاملاً در شنونده رویداد است، زمان به اینجا میرود.
5. آزمایش: مدت زمان پردازش
راههای تنظیم مجدد کار شنونده رویداد را امتحان کنید تا تأثیر آن را بر INP ببینید.
ابتدا UI را به روز کنید
چه اتفاقی میافتد اگر ترتیب تماسهای js را عوض کنید—ابتدا رابط کاربری را بهروزرسانی کنید، سپس مسدود کنید؟
کد کامل را ببینید: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
آیا متوجه شده اید که رابط کاربری زودتر ظاهر می شود؟ آیا ترتیب بر نمرات INP تأثیر می گذارد؟
سعی کنید ردیابی کنید و تعامل را بررسی کنید تا ببینید آیا تفاوتی وجود دارد یا خیر.
شنوندگان را جدا کنید
اگر کار را به یک شنونده رویداد جداگانه منتقل کنید چه؟ رابط کاربری را در یک شنونده رویداد بهروزرسانی کنید و صفحه را از یک شنونده جداگانه مسدود کنید.
مشاهده کد کامل: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
اکنون در پنل عملکرد چگونه به نظر می رسد؟
انواع مختلف رویداد
بیشتر تعاملات انواع مختلفی از رویدادها، از اشاره گر یا رویدادهای کلیدی، تا شناور، فوکوس/تار و رویدادهای مصنوعی مانند قبل از تغییر و قبل از ورودی را اجرا می کنند.
بسیاری از صفحات واقعی شنوندگان بسیاری از رویدادهای مختلف دارند.
اگر انواع رویداد را برای شنوندگان رویداد تغییر دهید، چه اتفاقی میافتد؟ به عنوان مثال، یکی از شنوندگان رویداد click
را با pointerup
یا mouseup
جایگزین کنید؟
کد کامل را ببینید: diff_handlers.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
بدون به روز رسانی UI
اگر تماس برای بهروزرسانی رابط کاربری را از شنونده رویداد حذف کنید، چه اتفاقی میافتد؟
مشاهده کد کامل: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
6. پردازش نتایج آزمایش مدت زمان
ردیابی عملکرد: ابتدا رابط کاربری را بهروزرسانی کنید
کد کامل را ببینید: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
با نگاهی به صفحه عملکرد ضبط شده با کلیک کردن روی دکمه، می بینید که نتایج تغییر نکرده است. در حالی که یک بهروزرسانی رابط کاربری قبل از کد مسدود کردن راهاندازی شده بود، مرورگر در واقع آنچه را که روی صفحه نمایش داده شده بود، بهروزرسانی نمیکرد تا زمانی که شنونده رویداد کامل شود، که به این معنی است که تکمیل تعامل هنوز بیش از یک ثانیه طول میکشد.
ردیابی عملکرد: شنوندگان جدا
مشاهده کد کامل: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
باز هم از نظر عملکردی هیچ تفاوتی وجود ندارد. تعامل هنوز یک ثانیه کامل طول می کشد.
اگر روی تعامل کلیک بزرگنمایی کنید، خواهید دید که در واقع دو تابع مختلف در نتیجه رویداد click
فراخوانی می شوند.
همانطور که انتظار می رفت، اولی – به روز رسانی رابط کاربری – بسیار سریع اجرا می شود، در حالی که دومی یک ثانیه کامل طول می کشد. با این حال، مجموع اثرات آنها منجر به همان تعامل آهسته برای کاربر نهایی می شود.
ردیابی عملکرد: انواع مختلف رویداد
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
این نتایج بسیار مشابه هستند. تعامل هنوز یک ثانیه کامل است. تنها تفاوت این است که شنونده click
کوتاهتر فقط بهروزرسانی UI اکنون بعد از شنونده pointerup
مسدودکننده اجرا میشود.
ردیابی عملکرد: بهروزرسانی رابط کاربری وجود ندارد
مشاهده کد کامل: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
- امتیاز به روز نمی شود، اما صفحه همچنان به روز می شود!
- انیمیشنها، جلوههای CSS، اقدامات پیشفرض اجزای وب (ورودی فرم)، ورود متن، برجستهسازی متن، همگی به روز رسانی ادامه میدهند.
در این حالت دکمه به حالت فعال می رود و با کلیک برمی گردد، که نیاز به رنگ آمیزی توسط مرورگر دارد، به این معنی که هنوز یک INP وجود دارد.
از آنجایی که شنونده رویداد موضوع اصلی را برای یک ثانیه مسدود کرد و از رنگ آمیزی صفحه جلوگیری کرد، تعامل همچنان یک ثانیه کامل طول می کشد.
ضبط پانل عملکرد، تعامل را تقریباً مشابه آنچه که قبلا انجام شده بود نشان می دهد.
غذای آماده
هر کدی که در هر رویداد شنونده اجرا شود، تعامل را به تاخیر می اندازد.
- این شامل شنوندگان ثبتشده از اسکریپتها و چارچوب یا کد کتابخانهای مختلف است که در شنوندهها اجرا میشود، مانند بهروزرسانی وضعیتی که رندر مؤلفه را راهاندازی میکند.
- نه تنها کد خود، بلکه تمام اسکریپت های شخص ثالث.
این یک مشکل رایج است!
در نهایت: فقط به این دلیل که کد شما یک رنگ را راهاندازی نمیکند، به این معنی نیست که یک رنگ منتظر شنوندگان رویداد آهسته برای تکمیل نخواهد بود.
7. آزمایش: تاخیر ورودی
کدهای طولانی در حال اجرا خارج از شنوندگان رویداد چطور؟ به عنوان مثال:
- اگر یک
<script>
دیر بارگیری داشتید که به طور تصادفی صفحه را در حین بارگذاری مسدود کرد. - یک تماس API، مانند
setInterval
، که به صورت دوره ای صفحه را مسدود می کند؟
سعی کنید blockFor
از شنونده رویداد حذف کرده و به یک setInterval()
اضافه کنید:
کد کامل را ببینید: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
چه اتفاقی می افتد؟
8. نتایج آزمایش تاخیر ورودی
کد کامل را ببینید: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
ضبط یک کلیک دکمهای که اتفاقاً در حین اجرای وظیفه مسدود کردن setInterval
رخ میدهد، منجر به تعامل طولانیمدت میشود، حتی بدون اینکه کار مسدودسازی در خود تعامل انجام شود!
این دوره های طولانی مدت اغلب وظایف طولانی نامیده می شوند.
با نگه داشتن نشانگر روی تعامل در DevTools، میتوانید ببینید که اکنون زمان تعامل عمدتاً به تاخیر ورودی نسبت داده میشود، نه مدت زمان پردازش.
توجه داشته باشید، همیشه بر تعاملات تأثیر نمی گذارد! اگر زمانی که کار در حال اجرا است کلیک نکنید، ممکن است خوش شانس باشید. چنین عطسههای «تصادفی» میتوانند کابوسهایی برای رفع اشکال باشند، در صورتی که فقط گاهی باعث ایجاد مشکل میشوند.
یکی از راههای ردیابی این موارد از طریق اندازهگیری وظایف طولانی (یا فریمهای انیمیشن طولانی ) و زمان مسدود کردن کل است.
9. ارائه کند
تا کنون، عملکرد جاوا اسکریپت را از طریق تاخیر ورودی یا شنوندگان رویداد بررسی کردهایم، اما چه چیز دیگری بر رندر رنگ بعدی تأثیر میگذارد؟
خوب، به روز رسانی صفحه با افکت های گران قیمت!
حتی اگر بهروزرسانی صفحه به سرعت انجام شود، ممکن است مرورگر همچنان مجبور باشد برای ارائه آنها سخت کار کند!
در تاپیک اصلی:
- فریمورکهای رابط کاربری که باید پس از تغییر حالت بهروزرسانیها را ارائه کنند
- تغییرات DOM یا جابجایی بسیاری از انتخابگرهای پرس و جو CSS گران قیمت میتواند تعداد زیادی Style، Layout و Paint را ایجاد کند.
خارج از تاپیک اصلی:
- استفاده از CSS برای تقویت جلوه های GPU
- افزودن تصاویر بسیار بزرگ با وضوح بالا
- استفاده از SVG/Canvas برای ترسیم صحنه های پیچیده
چند نمونه که معمولا در وب یافت می شود:
- یک سایت SPA که کل DOM را پس از کلیک کردن روی یک پیوند، بدون توقف برای ارائه بازخورد بصری اولیه، بازسازی میکند.
- صفحه جستجویی که فیلترهای جستجوی پیچیده ای را با رابط کاربری پویا ارائه می دهد، اما شنوندگان گران قیمت را برای انجام این کار اجرا می کند.
- تغییر حالت تاریک که سبک/طرح بندی کل صفحه را فعال می کند
10. آزمایش: تاخیر ارائه
Slow requestAnimationFrame
بیایید با استفاده از API requestAnimationFrame()
یک تاخیر ارائه طولانی را شبیه سازی کنیم.
تماس blockFor
را به یک requestAnimationFrame
انتقال دهید تا پس از بازگشت شنونده رویداد اجرا شود:
کد کامل را ببینید: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
چه اتفاقی می افتد؟
11. نتایج آزمایش تاخیر ارائه
کد کامل را ببینید: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
این تعامل یک ثانیه طولانی باقی می ماند، پس چه اتفاقی افتاد؟
requestAnimationFrame
قبل از رنگ بعدی درخواست پاسخ به تماس می کند. از آنجایی که INP زمان از تعامل تا رنگ بعدی را اندازه میگیرد، blockFor(1000)
در requestAnimationFrame
به مسدود کردن رنگ بعدی برای یک ثانیه کامل ادامه میدهد.
با این حال به دو نکته توجه کنید:
- در حالت شناور، خواهید دید که تمام زمان تعامل اکنون در «تاخیر ارائه» صرف میشود، زیرا مسدود کردن رشته اصلی پس از بازگشت شنونده رویداد اتفاق میافتد.
- ریشه فعالیت رشته اصلی دیگر رویداد کلیک نیست، بلکه "Animation Frame Fired" است.
12. تشخیص تعاملات
در این صفحه آزمایشی، پاسخگویی فوقالعاده بصری است، با امتیازات و تایمرها و رابط کاربری شمارنده... اما هنگام آزمایش صفحه متوسط، ظریفتر است.
وقتی تعامل طولانی می شود، همیشه مشخص نیست مقصر چیست. آیا این است:
- تاخیر ورودی؟
- مدت زمان پردازش رویداد؟
- تاخیر ارائه؟
در هر صفحه ای که بخواهید، می توانید از DevTools برای اندازه گیری پاسخگویی استفاده کنید. برای ایجاد عادت، این جریان را امتحان کنید:
- همانطور که معمولاً انجام می دهید در وب حرکت کنید.
- اختیاری: در حالی که برنامه افزودنی Web Vitals تعاملات را ثبت می کند، کنسول DevTools را باز بگذارید.
- اگر تعامل ضعیفی مشاهده کردید، سعی کنید آن را تکرار کنید:
- اگر نمیتوانید آن را تکرار کنید، از گزارشهای کنسول برای دریافت اطلاعات استفاده کنید.
- اگر می توانید آن را تکرار کنید، در پانل عملکرد ضبط کنید.
همه تاخیرها
سعی کنید کمی از همه این مشکلات را به صفحه اضافه کنید:
مشاهده کد کامل: all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
سپس از کنسول و پنل عملکرد برای تشخیص مشکلات استفاده کنید!
13. آزمایش: کار غیر همگام
از آنجایی که میتوانید جلوههای غیربصری را در تعاملات، مانند درخواستهای شبکه، راهاندازی تایمرها، یا فقط بهروزرسانی وضعیت جهانی شروع کنید، وقتی آنها در نهایت صفحه را بهروزرسانی کنند، چه اتفاقی میافتد؟
تا زمانی که رنگ بعدی پس از یک تعامل مجاز به رندر باشد، حتی اگر مرورگر تصمیم بگیرد که واقعاً به بهروزرسانی رندر جدیدی نیاز ندارد، اندازهگیری تعامل متوقف میشود.
برای امتحان کردن این کار، بهروزرسانی رابط کاربری را از شنونده کلیک ادامه دهید، اما کار مسدود کردن را از مهلت زمانی اجرا کنید.
مشاهده کد کامل: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
حالا چه اتفاقی می افتد؟
14. نتایج آزمایش کار ناهمگام
مشاهده کد کامل: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
تعامل اکنون کوتاه است زیرا رشته اصلی بلافاصله پس از بهروزرسانی رابط کاربری در دسترس است. کار مسدود کردن طولانی هنوز اجرا می شود، فقط مدتی بعد از رنگ اجرا می شود، بنابراین کاربر بازخورد فوری UI را دریافت می کند.
درس: اگر نمی توانید آن را حذف کنید، حداقل آن را حرکت دهید!
روش ها
آیا میتوانیم بهتر از یک setTimeout
100 میلیثانیهای ثابت انجام دهیم؟ احتمالاً همچنان میخواهیم که کد در سریعترین زمان ممکن اجرا شود، در غیر این صورت باید آن را حذف میکردیم!
هدف:
- تعامل
incrementAndUpdateUI()
اجرا خواهد شد. -
blockFor()
در اسرع وقت اجرا می شود، اما رنگ بعدی را مسدود نمی کند. - این منجر به رفتار قابل پیش بینی بدون "تایم اوت های جادویی" می شود.
برخی از راه های انجام این امر عبارتند از:
-
setTimeout(0)
-
Promise.then()
-
requestAnimationFrame
-
requestIdleCallback
-
scheduler.postTask()
"requestPostAnimationFrame"
برخلاف requestAnimationFrame
به تنهایی (که سعی میکند قبل از رنگ بعدی اجرا شود و معمولاً همچنان تعامل کندی ایجاد میکند)، requestAnimationFrame
+ setTimeout
یک پلیپری ساده برای requestPostAnimationFrame
ایجاد میکند که پس از رنگ بعدی، callback را اجرا میکند.
مشاهده کد کامل: raf+task.html
function afterNextPaint(callback) {
requestAnimationFrame(() => {
setTimeout(callback, 0);
});
}
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
afterNextPaint(() => {
blockFor(1000);
});
});
برای ارگونومی، حتی می توانید آن را در یک وعده بپیچید:
مشاهده کد کامل: raf+task2.html
async function nextPaint() {
return new Promise(resolve => afterNextPaint(resolve));
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await nextPaint();
blockFor(1000);
});
15. تعاملات متعدد (و کلیک های خشمگین)
جابجایی کارهای طولانی مسدود کردن می تواند کمک کند، اما این کارهای طولانی همچنان صفحه را مسدود می کند و بر تعاملات آینده و همچنین بسیاری از انیمیشن ها و به روز رسانی های صفحه دیگر تأثیر می گذارد.
نسخه کاری مسدودکننده غیرهمگام صفحه را مجدداً امتحان کنید (یا اگر در مرحله آخر به تغییر خود در مورد به تعویق انداختن کار رسیدید، نسخه خودتان را امتحان کنید):
مشاهده کد کامل: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
اگر سریع چند بار کلیک کنید چه اتفاقی می افتد؟
ردیابی عملکرد
برای هر کلیک، یک کار یک ثانیهای در صف قرار میگیرد که اطمینان حاصل میکند که موضوع اصلی برای مدت زمان قابلتوجهی مسدود شده است.
هنگامی که آن وظایف طولانی با کلیکهای جدید همپوشانی دارند، حتی اگر شنونده رویداد تقریباً بلافاصله برمیگردد، تعاملات آهسته ای ایجاد میشود. ما وضعیت مشابه آزمایش قبلی را با تأخیرهای ورودی ایجاد کردهایم. فقط این بار، تأخیر ورودی از یک setInterval
نیست، بلکه از کاری است که توسط شنوندگان رویداد قبلی راه اندازی شده است.
استراتژی ها
در حالت ایده آل، ما می خواهیم کارهای طولانی را به طور کامل حذف کنیم!
- کدهای غیر ضروری را به طور کلی حذف کنید - به خصوص اسکریپت ها.
- کد را بهینه کنید تا از اجرای کارهای طولانی جلوگیری کنید.
- هنگامی که تعاملات جدید از راه می رسد، کارهای قدیمی را متوقف کنید.
16. استراتژی 1: انحراف
یک استراتژی کلاسیک هر زمان که تعاملها بهسرعت انجام میشوند و پردازش یا اثرات شبکه گران هستند، شروع کار را عمداً به تأخیر بیندازید تا بتوانید لغو و راهاندازی مجدد کنید. این الگو برای رابط های کاربری مانند فیلدهای تکمیل خودکار مفید است.
- از
setTimeout
برای به تاخیر انداختن شروع کار گران قیمت، با تایمر، شاید 500 تا 1000 میلی ثانیه استفاده کنید. - پس از انجام این کار، شناسه تایمر را ذخیره کنید.
- اگر تعامل جدیدی وارد شد، تایمر قبلی را با استفاده از
clearTimeout
لغو کنید.
مشاهده کد کامل: debounce.html
let timer;
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
blockFor(1000);
}, 1000);
});
ردیابی عملکرد
با وجود چندین کلیک، فقط یک کار blockFor
به پایان می رسد و منتظر می ماند تا یک ثانیه کامل قبل از اجرا هیچ کلیکی وجود نداشته باشد. برای فعل و انفعالاتی که به صورت ناگهانی انجام می شوند - مانند تایپ کردن یک ورودی متن یا مواردی که انتظار می رود چندین کلیک سریع داشته باشند - این یک استراتژی ایده آل برای استفاده به طور پیش فرض است.
17. استراتژی 2: کار طولانی مدت را قطع کنید
هنوز این شانس بد شانسی وجود دارد که یک کلیک دیگر درست پس از سپری شدن دوره بازگرداندن، در میانه آن کار طولانی قرار گیرد و به دلیل تاخیر ورودی تبدیل به یک تعامل بسیار کند شود.
در حالت ایدهآل، اگر تعاملی در وسط کار ما رخ دهد، میخواهیم کار پرمشغله خود را متوقف کنیم تا هر گونه تعامل جدید فوراً رسیدگی شود. چگونه می توانیم این کار را انجام دهیم؟
برخی از APIها مانند isInputPending
وجود دارند، اما به طور کلی بهتر است وظایف طولانی را به تکههایی تقسیم کنید .
مقدار زیادی از setTimeout
s
اولین تلاش: یک کار ساده انجام دهید.
مشاهده کد کامل: small_tasks.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
});
});
این کار با اجازه دادن به مرورگر برای برنامهریزی هر کار به صورت جداگانه کار میکند و ورودی میتواند اولویت بیشتری داشته باشد!
ما به پنج ثانیه کامل کار برای پنج کلیک بازگشته ایم، اما هر کار یک ثانیه ای در هر کلیک به ده کار 100 میلی ثانیه ای تقسیم شده است. در نتیجه - حتی با تعاملات متعددی که با آن وظایف همپوشانی دارند - هیچ تعاملی دارای تاخیر ورودی بیش از 100 میلی ثانیه نیست! مرورگر شنوندگان رویداد ورودی را بر کار setTimeout
اولویت میدهد و تعاملات پاسخگو باقی میمانند.
این استراتژی بهویژه هنگام برنامهریزی نقاط ورودی جداگانه خوب عمل میکند - مثلاً اگر یک سری ویژگیهای مستقل دارید که باید در زمان بارگذاری برنامه تماس بگیرید. فقط بارگیری اسکریپت ها و اجرای همه چیز در زمان ارزیابی اسکریپت ممکن است به طور پیش فرض همه چیز را در یک کار طولانی غول پیکر اجرا کند.
با این حال، این استراتژی برای جدا کردن کدهای محکم جفت شده، مانند حلقه for
که از حالت اشتراکی استفاده می کند، به خوبی کار نمی کند.
اکنون با yield()
با این حال، میتوانیم از async
مدرن استفاده کنیم و await
تا به راحتی «نقاط بازده» را به هر تابع جاوا اسکریپت اضافه کنیم.
به عنوان مثال:
کد کامل را ببینید: yieldy.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldy(ms) {
const ms_per_part = 10;
const parts = ms / ms_per_part;
for (let i = 0; i < parts; i++) {
await schedulerDotYield();
blockFor(ms_per_part);
}
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await blockInPiecesYieldy(1000);
});
مانند قبل، رشته اصلی پس از مدتی کار به دست میآید و مرورگر میتواند به هرگونه تعامل ورودی پاسخ دهد، اما اکنون تنها چیزی که مورد نیاز است به جای setTimeout
جداگانه، یک await schedulerDotYield()
است که آن را به اندازه کافی ارگونومیک برای استفاده حتی در وسط یک حلقه for
.
اکنون با AbortContoller()
این کار موثر بود، اما هر تعاملی کارهای بیشتری را برنامه ریزی می کند، حتی اگر تعاملات جدیدی وارد شده باشد و ممکن است کاری را که باید انجام شود تغییر داده باشد.
با استراتژی بازگرداندن، با هر تعامل جدید، مهلت زمانی قبلی را لغو کردیم. آیا ما می توانیم چنین کاری را در اینجا انجام دهیم؟ یکی از راه های انجام این کار استفاده از AbortController()
است:
مشاهده کد کامل: aborty.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldyAborty(ms, signal) {
const parts = ms / 10;
for (let i = 0; i < parts; i++) {
// If AbortController has been asked to stop, abandon the current loop.
if (signal.aborted) return;
await schedulerDotYield();
blockFor(10);
}
}
let abortController = new AbortController();
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
abortController.abort();
abortController = new AbortController();
await blockInPiecesYieldyAborty(1000, abortController.signal);
});
هنگامی که یک کلیک وارد می شود، حلقه blockInPiecesYieldyAborty
را for
انجام هر کاری که باید انجام شود شروع می کند در حالی که به صورت دوره ای رشته اصلی را ارائه می دهد تا مرورگر به تعاملات جدید پاسخ دهد.
هنگامی که کلیک دوم وارد می شود، اولین حلقه به عنوان لغو شده با AbortController
علامت گذاری می شود و یک حلقه جدید blockInPiecesYieldyAborty
شروع می شود - دفعه بعد که اولین حلقه قرار است دوباره اجرا شود، متوجه می شود که signal.aborted
اکنون true
است و بلافاصله بدون بازگشت باز می گردد. انجام کارهای بیشتر
18. نتیجه گیری
شکستن تمام وظایف طولانی به سایت اجازه می دهد تا به تعاملات جدید پاسخگو باشد. این به شما امکان می دهد بازخورد اولیه را به سرعت ارائه دهید و همچنین به شما امکان می دهد تصمیماتی مانند سقط کردن کار در حال انجام را بگیرید. گاهی اوقات این بدان معناست که نقاط ورودی را به عنوان وظایف جداگانه زمان بندی کنید. گاهی اوقات به این معنی است که نقاط «بازده» را در جایی که راحت است اضافه کنید.
به یاد داشته باشید
- INP تمام تعاملات را اندازه گیری می کند.
- هر تعامل از ورودی تا رنگ بعدی اندازهگیری میشود - روشی که کاربر پاسخگویی را میبیند .
- تأخیر ورودی، مدت زمان پردازش رویداد و تأخیر ارائه ، همگی بر پاسخگویی تعامل تأثیر میگذارند.
- شما می توانید INP و خرابی های تعامل را با DevTools به راحتی اندازه گیری کنید!
استراتژی ها
- کد طولانی مدت (وظایف طولانی) در صفحات خود نداشته باشید.
- کدهای غیر ضروری را از شنوندگان رویداد تا بعد از رنگ بعدی خارج کنید.
- مطمئن شوید که بهروزرسانی رندر برای مرورگر کارآمد است.