Mengukur Interaksi dengan Next Paint (INP)

1. Pengantar

Ini adalah codelab interaktif untuk mempelajari cara mengukur Interaction to Next Paint (INP) menggunakan library web-vitals.

Prasyarat

Yang akan Anda pelajari

  • Cara menambahkan library web-vitals ke halaman Anda dan menggunakan data atribusinya.
  • Menggunakan data atribusi untuk mendiagnosis tempat dan cara mulai meningkatkan INP.

Yang akan Anda butuhkan

  • Komputer yang dapat meng-clone kode dari GitHub dan menjalankan perintah npm.
  • Editor teks.
  • Chrome versi terbaru agar semua pengukuran interaksi berfungsi.

2. Memulai persiapan

Mendapatkan dan menjalankan kode

Kode ini dapat ditemukan di repositori web-vitals-codelabs.

  1. Clone repo di terminal Anda: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git.
  2. Buka direktori yang di-clone: cd web-vitals-codelabs/measuring-inp.
  3. Instal dependensi: npm ci.
  4. Mulai server web: npm run start.
  5. Buka http://localhost:8080/ di browser Anda.

Mencoba halaman

Codelab ini menggunakan Gastropodicon (situs referensi anatomi siput yang populer) untuk mempelajari potensi masalah dengan INP.

Screenshot halaman demo Gastropodicon

Coba berinteraksi dengan halaman untuk merasakan interaksi mana yang lambat.

3. Mengenal Chrome DevTools

Buka DevTools dari menu More Tools > Developer Tools, dengan mengklik kanan halaman dan memilih Inspect, atau dengan menggunakan pintasan keyboard.

Dalam codelab ini, kita akan menggunakan panel Performance dan Console. Anda dapat beralih di antara keduanya di tab di bagian atas DevTools kapan saja.

  • Masalah INP paling sering terjadi di perangkat seluler, jadi beralihlah ke emulasi tampilan seluler.
  • Jika Anda melakukan pengujian di desktop atau laptop, performa kemungkinan akan jauh lebih baik daripada di perangkat seluler sebenarnya. Untuk melihat performa yang lebih realistis, klik ikon roda gigi di kanan atas panel Performance, lalu pilih CPU 4x slowdown.

Screenshot panel Performa DevTools bersama aplikasi, dengan pelambatan CPU 4x dipilih

4. Menginstal web-vitals

web-vitals adalah library JavaScript untuk mengukur metrik Web Vitals yang dialami pengguna Anda. Anda dapat menggunakan library ini untuk mengambil nilai tersebut, lalu mengirimkannya ke endpoint analisis untuk analisis nanti, untuk tujuan kami mengetahui kapan dan di mana interaksi lambat terjadi.

Ada beberapa cara berbeda untuk menambahkan library ke halaman. Cara Anda menginstal library di situs Anda sendiri akan bergantung pada cara Anda mengelola dependensi, proses build, dan faktor lainnya. Pastikan untuk memeriksa dokumen library untuk mengetahui semua opsi Anda.

Codelab ini akan menginstal dari npm dan memuat skrip secara langsung untuk menghindari proses build tertentu.

Ada dua versi web-vitals yang dapat Anda gunakan:

  • Build "standar" harus digunakan jika Anda ingin melacak nilai metrik Data Web Inti saat pemuatan halaman.
  • Build "atribusi" menambahkan informasi debug tambahan ke setiap metrik untuk mendiagnosis alasan metrik berakhir dengan nilai yang dimilikinya.

Untuk mengukur INP dalam codelab ini, kita memerlukan build atribusi.

Tambahkan web-vitals ke devDependencies project dengan menjalankan npm install -D web-vitals

Menambahkan web-vitals ke halaman:

Tambahkan versi atribusi skrip ke bagian bawah index.html dan catat hasilnya ke konsol:

<script type="module">
  import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';

  onINP(console.log);
</script>

Cobalah

Coba berinteraksi dengan halaman lagi saat konsol terbuka. Saat Anda mengklik halaman, tidak ada yang dicatat.

INP diukur di seluruh siklus proses halaman, sehingga secara default, web-vitals tidak melaporkan INP hingga pengguna keluar atau menutup halaman. Ini adalah perilaku ideal untuk mengirim sinyal untuk sesuatu seperti analisis, tetapi kurang ideal untuk melakukan debug secara interaktif.

web-vitals menyediakan opsi the reportAllChanges untuk pelaporan yang lebih detail. Jika diaktifkan, tidak setiap interaksi dilaporkan, tetapi setiap kali ada interaksi yang lebih lambat dari interaksi sebelumnya, interaksi tersebut akan dilaporkan.

Coba tambahkan opsi ke skrip dan berinteraksi dengan halaman lagi:

<script type="module">
  import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';

  onINP(console.log, {reportAllChanges: true});
</script>

Muat ulang halaman dan interaksi kini akan dilaporkan ke konsol, yang diperbarui setiap kali ada interaksi baru yang paling lambat. Misalnya, coba ketik di kotak penelusuran, lalu hapus input.

Screenshot konsol DevTools dengan pesan INP yang berhasil dicetak ke konsol

5. Apa yang ada dalam atribusi?

Mari kita mulai dengan interaksi pertama yang akan dilakukan sebagian besar pengguna dengan halaman, yaitu dialog izin cookie.

Banyak halaman akan memiliki skrip yang memerlukan cookie yang dipicu secara sinkron saat cookie diterima oleh pengguna, sehingga menyebabkan klik menjadi interaksi yang lambat. Itulah yang terjadi di sini.

Klik Yes untuk menerima cookie (demo), dan lihat data INP yang kini dicatat ke konsol DevTools.

Objek data INP dicatat ke konsol DevTools

Informasi tingkat atas ini tersedia dalam build web-vitals standar dan atribusi:

{
  name: 'INP',
  value: 344,
  rating: 'needs-improvement',
  entries: [...],
  id: 'v4-1715732159298-8028729544485',
  navigationType: 'reload',
  attribution: {...},
}

Durasi waktu mulai dari saat pengguna mengklik hingga paint berikutnya adalah 344 milidetik—"perlu ditingkatkan" INP. Array entries memiliki semua nilai PerformanceEntry yang terkait dengan interaksi ini—dalam hal ini, hanya satu peristiwa klik.

Namun, untuk mengetahui apa yang terjadi selama waktu ini, kita paling tertarik dengan properti attribution. Untuk membuat data atribusi, web-vitals menemukan Long Animations Frame (LoAF) yang tumpang-tindih dengan peristiwa klik. LoAF kemudian dapat memberikan data mendetail tentang waktu yang dihabiskan selama frame tersebut, mulai dari skrip yang dijalankan, hingga waktu yang dihabiskan dalam callback requestAnimationFrame, gaya, dan tata letak.

Luaskan properti attribution untuk melihat informasi selengkapnya. Data ini jauh lebih lengkap.

attribution: {
  interactionTargetElement: Element,
  interactionTarget: '#confirm',
  interactionType: 'pointer',

  inputDelay: 27,
  processingDuration: 295.6,
  presentationDelay: 21.4,

  processedEventEntries: [...],
  longAnimationFrameEntries: [...],
}

Pertama, ada informasi tentang apa yang diinteraksikan:

  • interactionTargetElement: referensi langsung ke elemen yang diinteraksikan (jika elemen belum dihapus dari DOM).
  • interactionTarget: pemilih untuk menemukan elemen dalam halaman.

Selanjutnya, waktu dipecah dengan cara tingkat tinggi:

  • inputDelay: waktu antara saat pengguna memulai interaksi (misalnya, mengklik mouse) dan saat pemroses peristiwa untuk interaksi tersebut mulai berjalan. Dalam hal ini, penundaan input hanya sekitar 27 milidetik, bahkan dengan pembatasan CPU diaktifkan.
  • processingDuration: waktu yang diperlukan pemroses peristiwa untuk berjalan hingga selesai. Sering kali, halaman akan memiliki beberapa pemroses untuk satu peristiwa (misalnya, pointerdown, pointerup, dan click). Jika semuanya berjalan dalam frame animasi yang sama, semuanya akan digabungkan ke dalam waktu ini. Dalam hal ini, durasi pemrosesan memerlukan waktu 295,6 milidetik—sebagian besar waktu INP.
  • presentationDelay: waktu dari saat pemroses peristiwa selesai hingga saat browser selesai melukis frame berikutnya. Dalam hal ini, 21,4 milidetik.

Fase INP ini dapat menjadi sinyal penting untuk mendiagnosis hal yang perlu dioptimalkan. Panduan Mengoptimalkan INP memiliki informasi selengkapnya tentang subjek ini.

Jika kita melihat lebih dalam, processedEventEntries berisi lima peristiwa, bukan satu peristiwa dalam array entries INP tingkat atas. Apa perbedaannya?

processedEventEntries: [
  {
    name: 'mouseover',
    entryType: 'event',
    startTime: 1801.6,
    duration: 344,
    processingStart: 1825.3,
    processingEnd: 1825.3,
    cancelable: true
  },
  {
    name: 'mousedown',
    entryType: 'event',
    startTime: 1801.6,
    duration: 344,
    processingStart: 1825.3,
    processingEnd: 1825.3,
    cancelable: true
  },
  {name: 'mousedown', ...},
  {name: 'mouseup', ...},
  {name: 'click', ...},
],

Entri tingkat atas adalah peristiwa INP, dalam hal ini klik. processedEventEntries atribusi adalah semua peristiwa yang diproses selama frame yang sama. Perhatikan bahwa peristiwa ini mencakup peristiwa lain seperti mouseover dan mousedown, bukan hanya peristiwa klik. Mengetahui peristiwa lain ini dapat menjadi penting jika peristiwa tersebut juga lambat, karena semuanya berkontribusi pada responsivitas yang lambat.

Terakhir, ada array longAnimationFrameEntries. Ini mungkin satu entri, tetapi ada kasus ketika interaksi dapat tersebar di beberapa frame. Di sini kita memiliki kasus paling sederhana dengan satu frame animasi panjang.

longAnimationFrameEntries

Memperluas entri LoAF:

longAnimationFrameEntries: [{
  name: 'long-animation-frame',
  startTime: 1823,
  duration: 319,

  renderStart: 2139.5,
  styleAndLayoutStart: 2139.7,
  firstUIEventTimestamp: 1801.6,
  blockingDuration: 268,

  scripts: [{...}]
}],

Ada sejumlah nilai berguna di sini, seperti memecah jumlah waktu yang dihabiskan untuk gaya. Artikel Long Animation Frames API membahas properti ini secara lebih mendalam. Saat ini, kita terutama tertarik dengan properti scripts, yang berisi entri yang memberikan detail tentang skrip yang bertanggung jawab atas frame yang berjalan lama:

scripts: [{
  name: 'script',
  invoker: 'BUTTON#confirm.onclick',
  invokerType: 'event-listener',

  startTime: 1828.6,
  executionStart: 1828.6,
  duration: 294,

  sourceURL: 'http://localhost:8080/third-party/cmp.js',
  sourceFunctionName: '',
  sourceCharPosition: 1144
}]

Dalam hal ini, kita dapat mengetahui bahwa waktu terutama dihabiskan dalam satu event-listener, yang dipanggil di BUTTON#confirm.onclick. Kita bahkan dapat melihat URL sumber skrip dan posisi karakter tempat fungsi ditentukan.

Bawa pulang

Apa yang dapat ditentukan tentang kasus ini dari data atribusi ini?

  • Interaksi dipicu oleh klik pada elemen button#confirm (dari attribution.interactionTarget dan properti invoker pada entri atribusi skrip).
  • Waktu dihabiskan terutama untuk menjalankan pemroses peristiwa (dari attribution.processingDuration dibandingkan dengan value metrik total).
  • Kode pemroses peristiwa yang lambat dimulai dari pemroses klik yang ditentukan di third-party/cmp.js (dari scripts.sourceURL).

Data tersebut cukup untuk mengetahui tempat kita perlu melakukan pengoptimalan.

6. Beberapa pemroses peristiwa

Muat ulang halaman agar konsol DevTools kosong dan interaksi izin cookie tidak lagi menjadi interaksi terlama.

Mulai ketik di kotak penelusuran. Apa yang ditampilkan data atribusi? Menurut Anda, apa yang terjadi?

Data atribusi

Pertama, pemindaian tingkat tinggi dari satu contoh pengujian demo:

{
  name: 'INP',
  value: 1072,
  rating: 'poor',
  attribution: {
    interactionTargetElement: Element,
    interactionTarget: '#search-terms',
    interactionType: 'keyboard',

    inputDelay: 3.3,
    processingDuration: 1060.6,
    presentationDelay: 8.1,

    processedEventEntries: [...],
    longAnimationFrameEntries: [...],
  }
}

Ini adalah nilai INP yang buruk (dengan pembatasan CPU diaktifkan) dari interaksi keyboard dengan elemen input#search-terms. Sebagian besar waktu—1061 milidetik dari total INP 1072 milidetik—dihabiskan dalam durasi pemrosesan.

Namun, entri scripts lebih menarik.

Layout thrashing

Entri pertama array scripts memberi kita beberapa konteks yang berharga:

scripts: [{
  name: 'script',
  invoker: 'BUTTON#confirm.onclick',
  invokerType: 'event-listener',

  startTime: 4875.6,
  executionStart: 4875.6,
  duration: 497,
  forcedStyleAndLayoutDuration: 388,

  sourceURL: 'http://localhost:8080/js/index.js',
  sourceFunctionName: 'handleSearch',
  sourceCharPosition: 940
},
...]

Sebagian besar durasi pemrosesan terjadi selama eksekusi skrip ini, yang merupakan pemroses input (pemanggilnya adalah INPUT#search-terms.oninput). Nama fungsi diberikan (handleSearch), seperti posisi karakter di dalam file sumber index.js.

Namun, ada properti baru: forcedStyleAndLayoutDuration. Ini adalah waktu yang dihabiskan dalam pemanggilan skrip ini saat browser dipaksa untuk menata ulang halaman. Dengan kata lain, 78% waktu—388 milidetik dari 497—yang dihabiskan untuk menjalankan pemroses peristiwa ini sebenarnya dihabiskan dalam layout thrashing.

Hal ini harus menjadi prioritas utama untuk diperbaiki.

Pemroses berulang

Secara individual, tidak ada yang terlalu luar biasa tentang dua entri skrip berikutnya:

scripts: [...,
{
  name: 'script',
  invoker: '#document.onkeyup',
  invokerType: 'event-listener',

  startTime: 5375.3,
  executionStart: 5375.3,
  duration: 124,

  sourceURL: 'http://localhost:8080/js/index.js',
  sourceFunctionName: '',
  sourceCharPosition: 1526,
},
{
  name: 'script',
  invoker: '#document.onkeyup',
  invokerType: 'event-listener',

  startTime: 5673.9,
  executionStart: 5673.9,
  duration: 95,

  sourceURL: 'http://localhost:8080/js/index.js',
  sourceFunctionName: '',
  sourceCharPosition: 1526
}]

Kedua entri adalah pemroses keyup, yang menjalankan satu setelah yang lain. Pemroses adalah fungsi anonim (sehingga tidak ada yang dilaporkan dalam properti sourceFunctionName), tetapi kita masih memiliki file sumber dan posisi karakter, sehingga kita dapat menemukan lokasi kode.

Yang aneh adalah keduanya berasal dari file sumber dan posisi karakter yang sama.

Browser akhirnya memproses beberapa penekanan tombol dalam satu frame animasi, sehingga pemroses peristiwa ini berjalan dua kali sebelum ada yang dapat dilukis.

Efek ini juga dapat digabungkan, yang mana semakin lama pemroses peristiwa selesai, semakin banyak peristiwa input tambahan yang dapat masuk, sehingga memperpanjang interaksi yang lambat.

Karena ini adalah interaksi penelusuran/pelengkapan otomatis, debouncing input akan menjadi strategi yang baik sehingga paling banyak, satu penekanan tombol diproses per frame.

7. Penundaan input

Alasan umum penundaan input—waktu dari saat pengguna berinteraksi hingga saat pemroses peristiwa dapat mulai memproses interaksi—adalah karena thread utama sedang sibuk. Hal ini dapat memiliki beberapa penyebab:

  • Halaman sedang dimuat dan thread utama sedang sibuk melakukan pekerjaan awal untuk menyiapkan DOM, menata letak dan menata gaya halaman, serta mengevaluasi dan menjalankan skrip.
  • Halaman umumnya sibuk—misalnya, menjalankan komputasi, animasi berbasis skrip, atau iklan.
  • Interaksi sebelumnya memerlukan waktu yang sangat lama untuk diproses sehingga menunda interaksi mendatang yang terlihat dalam contoh terakhir.

Halaman demo memiliki fitur rahasia yang akan mulai menganimasikan dan melakukan beberapa pekerjaan JavaScript thread utama yang berat jika Anda mengklik logo siput di bagian atas halaman.

  • Klik logo siput untuk memulai animasi.
  • Tugas JavaScript dipicu saat siput berada di bagian bawah pantulan. Coba berinteraksi dengan halaman sedekat mungkin dengan bagian bawah pantulan dan lihat seberapa tinggi INP yang dapat Anda picu.

Misalnya, meskipun Anda tidak memicu pemroses peristiwa lain—seperti dari mengklik dan memfokuskan kotak penelusuran saat siput memantul—pekerjaan thread utama akan menyebabkan halaman tidak responsif selama jangka waktu yang signifikan.

Di banyak halaman, pekerjaan thread utama yang berat tidak akan berperilaku baik, tetapi ini adalah demonstrasi yang baik untuk melihat cara mengidentifikasinya dalam data atribusi INP.

Berikut adalah contoh atribusi dari hanya memfokuskan kotak penelusuran selama pantulan siput:

{
  name: 'INP',
  value: 728,
  rating: 'poor',

  attribution: {
    interactionTargetElement: Element,
    interactionTarget: '#search-terms',
    interactionType: 'pointer',

    inputDelay: 702.3,
    processingDuration: 4.9,
    presentationDelay: 20.8,

    longAnimationFrameEntries: [{
      name: 'long-animation-frame',
      startTime: 2064.8,
      duration: 790,

      renderStart: 2065,
      styleAndLayoutStart: 2854.2,
      firstUIEventTimestamp: 0,
      blockingDuration: 740,

      scripts: [{...}]
    }]
  }
}

Seperti yang diprediksi, pemroses peristiwa dieksekusi dengan cepat—menampilkan durasi pemrosesan 4,9 milidetik, dan sebagian besar interaksi yang buruk dihabiskan dalam penundaan input, yang memerlukan waktu 702,3 milidetik dari total 728.

Situasi ini dapat sulit di-debug. Meskipun kita tahu apa yang diinteraksikan pengguna dan bagaimana, kita juga tahu bahwa bagian interaksi tersebut selesai dengan cepat dan tidak menjadi masalah. Sebaliknya, ada hal lain di halaman yang menunda interaksi dari awal pemrosesan, tetapi bagaimana kita tahu tempat untuk mulai mencari?

Entri skrip LoAF ada di sini untuk menyelamatkan:

scripts: [{
  name: 'script',
  invoker: 'SPAN.onanimationiteration',
  invokerType: 'event-listener',

  startTime: 2065,
  executionStart: 2065,
  duration: 788,

  sourceURL: 'http://localhost:8080/js/index.js',
  sourceFunctionName: 'cryptodaphneCoinHandler',
  sourceCharPosition: 1831
}]

Meskipun fungsi ini tidak ada hubungannya dengan interaksi, fungsi ini memperlambat frame animasi, sehingga disertakan dalam data LoAF yang digabungkan dengan peristiwa interaksi.

Dari sini, kita dapat melihat bagaimana fungsi yang menunda pemrosesan interaksi dipicu (oleh pemroses animationiteration), fungsi mana yang bertanggung jawab, dan lokasinya di file sumber kita.

8. Penundaan presentasi: saat pembaruan tidak akan dilukis

Penundaan presentasi mengukur waktu dari saat pemroses peristiwa selesai berjalan hingga browser dapat melukis frame baru ke layar, yang menampilkan masukan yang terlihat oleh pengguna.

Muat ulang halaman untuk mereset nilai INP lagi, lalu buka menu hamburger. Ada gangguan yang jelas saat menu hamburger dibuka.

Seperti apa bentuknya?

{
  name: 'INP',
  value: 376,
  rating: 'needs-improvement',
  delta: 352,

  attribution: {
    interactionTarget: '#sidenav-button>svg',
    interactionType: 'pointer',

    inputDelay: 12.8,
    processingDuration: 14.7,
    presentationDelay: 348.5,

    longAnimationFrameEntries: [{
      name: 'long-animation-frame',
      startTime: 651,
      duration: 365,

      renderStart: 673.2,
      styleAndLayoutStart: 1004.3,
      firstUIEventTimestamp: 138.6,
      blockingDuration: 315,

      scripts: [{...}]
    }]
  }
}

Kali ini penundaan presentasi yang merupakan sebagian besar interaksi yang lambat. Artinya, apa pun yang memblokir thread utama terjadi setelah pemroses peristiwa selesai.

scripts: [{
  entryType: 'script',
  invoker: 'FrameRequestCallback',
  invokerType: 'user-callback',

  startTime: 673.8,
  executionStart: 673.8,
  duration: 330,

  sourceURL: 'http://localhost:8080/js/side-nav.js',
  sourceFunctionName: '',
  sourceCharPosition: 1193,
}]

Melihat satu entri dalam array scripts, kita melihat waktu yang dihabiskan dalam user-callback dari FrameRequestCallback. Kali ini, penundaan presentasi disebabkan oleh callback requestAnimationFrame.

9. Kesimpulan

Mengagregasi data kolom

Perlu diketahui bahwa semuanya lebih mudah saat melihat satu entri atribusi INP dari satu pemuatan halaman. Bagaimana data ini dapat diagregasi untuk melakukan debug INP berdasarkan data kolom? Jumlah detail yang bermanfaat justru membuat hal ini menjadi lebih sulit.

Misalnya, sangat berguna untuk mengetahui elemen halaman mana yang merupakan sumber umum interaksi yang lambat. Namun, jika halaman Anda memiliki nama class CSS yang dikompilasi yang berubah dari build ke build, pemilih web-vitals dari elemen yang sama mungkin berbeda di seluruh build.

Sebagai gantinya, Anda harus memikirkan aplikasi tertentu untuk menentukan hal yang paling berguna dan cara data dapat diagregasi. Misalnya, sebelum mengirim sinyal data atribusi kembali, Anda dapat mengganti pemilih web-vitals dengan ID Anda sendiri, berdasarkan komponen tempat target berada, atau peran ARIA yang dipenuhi target.

Demikian pula, entri scripts mungkin memiliki hash berbasis file di jalur sourceURL yang membuatnya sulit digabungkan, tetapi Anda dapat menghapus hash berdasarkan proses build yang diketahui sebelum mengirim sinyal data kembali.

Sayangnya, tidak ada jalur yang mudah dengan data yang kompleks ini, tetapi bahkan menggunakan subset data ini lebih berharga daripada tidak ada data atribusi sama sekali untuk proses debug.

Atribusi di mana saja

Atribusi INP berbasis LoAF adalah alat bantu debug yang canggih. Alat ini menawarkan data terperinci tentang apa yang terjadi secara khusus selama INP. Dalam banyak kasus, alat ini dapat mengarahkan Anda ke lokasi yang tepat dalam skrip tempat Anda harus memulai upaya pengoptimalan.

Sekarang Anda siap menggunakan data atribusi INP di situs mana pun.

Meskipun Anda tidak memiliki akses untuk mengedit halaman, Anda dapat membuat ulang proses dari codelab ini dengan menjalankan cuplikan berikut di konsol DevTools untuk melihat apa yang dapat Anda temukan:

const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js';
script.onload = function () {
  webVitals.onINP(console.log, {reportAllChanges: true});
};
document.head.appendChild(script);

Pelajari lebih lanjut