Przejdź do treści
Wydajność

Interaction to Next Paint (INP)

Opublikowano:
Aktualizacja:

Interaction to Next Paint (INP) mierzy responsywność strony: czas od interakcji użytkownika (kliknięcia, dotknięcia lub naciśnięcia klawisza) do następnego odmalowania ekranu, w którym widać efekt tej interakcji. Wynik strony to zbliżone do najgorszego opóźnienie zaobserwowane podczas całej wizyty.

W marcu 2024 INP zastąpił First Input Delay (FID) jako metryka Core Web Vitals, bo mierzy pełny koszt obsługi interakcji, a nie tylko opóźnienie jej rozpoczęcia. Dobry wynik to maksymalnie 200 ms na 75. percentylu.

Trzy fazy każdej interakcji

  • Opóźnienie wejścia: czekanie, aż główny wątek będzie wolny, by uruchomić handler.
  • Czas przetwarzania: wykonanie kodu obsługującego zdarzenie.
  • Opóźnienie prezentacji: przeliczenie layoutu i odmalowanie kolejnej klatki.

Jak poprawić INP

Kluczem jest odciążenie głównego wątku: dzielenie długich zadań (yielding), redukcja i odroczenie JavaScriptu, unikanie nadmiarowych re-renderów oraz ograniczenie kosztownych operacji na DOM w handlerach zdarzeń.

Przykład

Przykład edukacyjny: pokaż stan pracy i oddaj sterowanie przed kolejnymi porcjami. scheduler.yield() lub fallback setTimeout tworzy możliwość obsługi innych zadań; samo await requestAnimationFrame nie gwarantuje odmalowania. Dobierz porcje z profilu, obsłuż błędy i ponowne kliknięcia. Szybka informacja wizualna nie oznacza końca całej operacji asynchronicznej.

Pogarsza INP: synchroniczna pętla w handlerze
JavaScript
button.addEventListener('click', () => {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]); // blokuje główny wątek
  }
  updateUI();
});
Lepsze: dzielenie zadania (yield)
JavaScript
const yieldTask = () => globalThis.scheduler?.yield
  ? globalThis.scheduler.yield()
  : new Promise(resolve => setTimeout(resolve, 0));

button.addEventListener('click', async () => {
  button.disabled = true;
  status.textContent = 'Przetwarzanie…';
  try {
    await yieldTask();
    for (let i = 0; i < items.length; i += 100) {
      for (const item of items.slice(i, i + 100)) processItem(item);
      await yieldTask();
    }
    updateUI();
    status.textContent = 'Gotowe';
  } catch {
    status.textContent = 'Nie udało się ukończyć operacji';
  } finally {
    button.disabled = false;
  }
});

Źródła i dalsza lektura

Sprawdź wydajność swojej strony

Insight mierzy Core Web Vitals i porządkuje wąskie gardła według wpływu.