درک تعامل با رنگ بعدی (INP)

1. معرفی

یک نسخه نمایشی و کدهای تعاملی برای یادگیری در مورد Interaction to Next Paint (INP) .

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

پیش نیازها

  • آشنایی با توسعه HTML و جاوا اسکریپت.
  • توصیه می شود: اسناد INP را بخوانید.

چیزی که یاد می گیرید

  • نحوه تأثیر متقابل تعاملات کاربر و مدیریت شما از این تعاملات بر پاسخگویی صفحه.
  • نحوه کاهش و حذف تاخیرها برای تجربه کاربری روان.

آنچه شما نیاز دارید

  • رایانه ای با قابلیت شبیه سازی کد از GitHub و اجرای دستورات npm.
  • یک ویرایشگر متن
  • نسخه اخیر Chrome برای کارکرد همه اندازه‌گیری‌های تعامل.

2. راه اندازی شوید

کد را دریافت و اجرا کنید

کد در مخزن web-vitals-codelabs یافت می شود.

  1. مخزن را در ترمینال خود کلون کنید: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. عبور از دایرکتوری شبیه سازی شده: cd web-vitals-codelabs/understanding-inp
  3. وابستگی ها را نصب کنید: npm ci
  4. سرور وب را راه اندازی کنید: npm run start
  5. در مرورگر خود از http://localhost:5173/understanding-inp/ دیدن کنید

نمای کلی برنامه

در بالای صفحه یک دکمه شمارنده امتیاز و افزایش وجود دارد. یک نسخه نمایشی کلاسیک از واکنش پذیری و پاسخگویی!

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

در زیر دکمه چهار اندازه گیری وجود دارد:

  • INP: امتیاز INP فعلی، که معمولاً بدترین تعامل است.
  • تعامل: امتیاز آخرین تعامل.
  • FPS: فریم های رشته اصلی صفحه در هر ثانیه.
  • تایمر: یک انیمیشن تایمر در حال اجرا برای کمک به تجسم jank.

ورودی‌های FPS و Timer اصلاً برای اندازه‌گیری تعاملات ضروری نیستند. آنها فقط برای تجسم پاسخگویی کمی ساده تر اضافه شده اند.

آن را امتحان کنید

سعی کنید با دکمه افزایش تعامل داشته باشید و شاهد افزایش امتیاز باشید. آیا مقادیر INP و Interaction با هر افزایش تغییر می کند؟

INP اندازه گیری می کند که از لحظه تعامل کاربر تا زمانی که صفحه واقعاً به روز رسانی ارائه شده را به کاربر نشان دهد چقدر طول می کشد.

3. سنجش تعامل با Chrome DevTools

DevTools را از منوی More Tools > Developer Tools با کلیک راست روی صفحه و انتخاب Inspect یا با استفاده از میانبر صفحه کلید باز کنید.

به پانل Performance بروید، که از آن برای اندازه گیری تعاملات استفاده خواهید کرد.

تصویری از پنل DevTools Performance در کنار برنامه

سپس، یک تعامل را در پانل عملکرد ثبت کنید.

  1. ضبط را فشار دهید.
  2. تعامل با صفحه (دکمه Increment را فشار دهید).
  3. ضبط را متوقف کنید.

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

نمایشی متحرک از ضبط یک تعامل با استفاده از پنل عملکرد DevTools

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

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

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

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، می‌توانید ببینید که اکنون زمان تعامل عمدتاً به تاخیر ورودی نسبت داده می‌شود، نه مدت زمان پردازش.

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

توجه داشته باشید، همیشه بر تعاملات تأثیر نمی گذارد! اگر زمانی که کار در حال اجرا است کلیک نکنید، ممکن است خوش شانس باشید. چنین عطسه‌های «تصادفی» می‌توانند کابوس‌هایی برای رفع اشکال باشند، در صورتی که فقط گاهی باعث ایجاد مشکل می‌شوند.

یکی از راه‌های ردیابی این موارد از طریق اندازه‌گیری وظایف طولانی (یا فریم‌های انیمیشن طولانی ) و زمان مسدود کردن کل است.

9. ارائه کند

تا کنون، عملکرد جاوا اسکریپت را از طریق تاخیر ورودی یا شنوندگان رویداد بررسی کرده‌ایم، اما چه چیز دیگری بر رندر رنگ بعدی تأثیر می‌گذارد؟

خوب، به روز رسانی صفحه با افکت های گران قیمت!

حتی اگر به‌روزرسانی صفحه به سرعت انجام شود، ممکن است مرورگر همچنان مجبور باشد برای ارائه آنها سخت کار کند!

در تاپیک اصلی:

  • فریمورک‌های رابط کاربری که باید پس از تغییر حالت به‌روزرسانی‌ها را ارائه کنند
  • تغییرات DOM یا جابجایی بسیاری از انتخابگرهای پرس و جو CSS گران قیمت می‌تواند تعداد زیادی Style، Layout و Paint را ایجاد کند.

خارج از تاپیک اصلی:

  • استفاده از CSS برای تقویت جلوه های GPU
  • افزودن تصاویر بسیار بزرگ با وضوح بالا
  • استفاده از SVG/Canvas برای ترسیم صحنه های پیچیده

طرحی از عناصر مختلف رندر در وب

RenderingNG

چند نمونه که معمولا در وب یافت می شود:

  • یک سایت 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 برای اندازه گیری پاسخگویی استفاده کنید. برای ایجاد عادت، این جریان را امتحان کنید:

  1. همانطور که معمولاً انجام می دهید در وب حرکت کنید.
  2. اختیاری: در حالی که برنامه افزودنی Web Vitals تعاملات را ثبت می کند، کنسول DevTools را باز بگذارید.
  3. اگر تعامل ضعیفی مشاهده کردید، سعی کنید آن را تکرار کنید:
  • اگر نمی‌توانید آن را تکرار کنید، از گزارش‌های کنسول برای دریافت اطلاعات استفاده کنید.
  • اگر می توانید آن را تکرار کنید، در پانل عملکرد ضبط کنید.

همه تاخیرها

سعی کنید کمی از همه این مشکلات را به صفحه اضافه کنید:

مشاهده کد کامل: 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);
});

یک برهمکنش 27 میلی ثانیه ای با یک کار طولانی یک ثانیه که اکنون بعداً در ردیابی رخ می دهد

تعامل اکنون کوتاه است زیرا رشته اصلی بلافاصله پس از به‌روزرسانی رابط کاربری در دسترس است. کار مسدود کردن طولانی هنوز اجرا می شود، فقط مدتی بعد از رنگ اجرا می شود، بنابراین کاربر بازخورد فوری 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);
});

اگر سریع چند بار کلیک کنید چه اتفاقی می افتد؟

ردیابی عملکرد

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

چندین کار طولانی مدت در رشته اصلی، که باعث تعامل با سرعت 800 میلی‌ثانیه می‌شود

هنگامی که آن وظایف طولانی با کلیک‌های جدید همپوشانی دارند، حتی اگر شنونده رویداد تقریباً بلافاصله برمی‌گردد، تعاملات آهسته ای ایجاد می‌شود. ما وضعیت مشابه آزمایش قبلی را با تأخیرهای ورودی ایجاد کرده‌ایم. فقط این بار، تأخیر ورودی از یک 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 به راحتی اندازه گیری کنید!

استراتژی ها

  • کد طولانی مدت (وظایف طولانی) در صفحات خود نداشته باشید.
  • کدهای غیر ضروری را از شنوندگان رویداد تا بعد از رنگ بعدی خارج کنید.
  • مطمئن شوید که به‌روزرسانی رندر برای مرورگر کارآمد است.

بیشتر بدانید