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.
  • Gunakan 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. Buat 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 INP.

Screenshot halaman demo Gastropodicon

Coba berinteraksi dengan halaman untuk merasakan interaksi mana yang lambat.

3. Mengenal Chrome DevTools

Buka DevTools dari menu Alat Lainnya > Alat Developer, dengan mengklik kanan halaman dan memilih Periksa, atau dengan menggunakan pintasan keyboard.

Dalam codelab ini, kita akan menggunakan panel Performa dan Konsol. 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 sungguhan. Untuk melihat performa yang lebih realistis, klik ikon roda gigi di kanan atas panel Performa, 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 Data Web yang dialami pengguna Anda. Anda dapat menggunakan library untuk merekam nilai tersebut, lalu mengirimkannya ke endpoint Analytics untuk dianalisis nanti, untuk tujuan kami yaitu mengetahui kapan dan di mana interaksi lambat terjadi.

Ada beberapa cara untuk menambahkan pustaka 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 pustaka untuk mengetahui semua opsi Anda.

Codelab ini akan menginstal dari npm dan memuat skrip secara langsung untuk menghindari mempelajari 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 pada 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 menginginkan build atribusi.

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

Tambahkan 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 dengan konsol terbuka. Saat Anda mengklik halaman, tidak ada yang dicatat.

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

web-vitals menyediakan opsi reportAllChanges untuk pelaporan yang lebih lengkap. 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 yang paling lambat baru. 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 serentak saat cookie diterima oleh pengguna, sehingga menyebabkan klik menjadi interaksi yang lambat. Itulah yang terjadi di sini.

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

Objek data INP dicatat ke konsol DevTools

Informasi tingkat teratas ini tersedia di 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—INP "perlu ditingkatkan". 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, kami paling tertarik dengan properti attribution. Untuk membuat data atribusi, web-vitals menemukan Frame Animasi Panjang (LoAF) mana yang tumpang-tindih dengan peristiwa klik. LoAF kemudian dapat memberikan data mendetail tentang penggunaan waktu 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. Datanya 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 berinteraksi:

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

Selanjutnya, pengaturan waktu dipecah secara umum:

  • 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.
  • 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 sejak pemroses peristiwa selesai hingga waktu browser selesai menggambar frame berikutnya. Dalam hal ini, 21,4 milidetik.

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

Jika ditelusuri lebih dalam, processedEventEntries berisi lima peristiwa, bukan satu peristiwa dalam array INP entries tingkat teratas. 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 teratas adalah peristiwa INP the, dalam hal ini adalah klik. Atribusi processedEventEntries 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 bisa sangat penting jika peristiwa tersebut juga lambat, karena semuanya berkontribusi terhadap respons yang lambat.

Terakhir, ada array longAnimationFrameEntries. Ini mungkin berupa 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 penataan 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.

Kesimpulan

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 terutama dihabiskan untuk mengeksekusi pemroses peristiwa (dari attribution.processingDuration dibandingkan dengan total metrik value).
  • Kode pemroses peristiwa lambat dimulai dari pemroses klik yang ditentukan di third-party/cmp.js (dari scripts.sourceURL).

Data tersebut sudah cukup untuk mengetahui area yang perlu dioptimalkan.

6. Beberapa pemroses peristiwa

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

Mulai mengetik di kotak penelusuran. Apa yang ditampilkan data atribusi? Menurut Anda, apa yang sedang 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 throttling CPU diaktifkan) dari interaksi keyboard dengan elemen input#search-terms. Sebagian besar waktu—1.061 milidetik dari total INP 1.072 milidetik—dihabiskan dalam durasi pemrosesan.

Namun, entri scripts lebih menarik.

Thrashing tata letak

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), begitu juga 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 mengeksekusi pemroses peristiwa ini sebenarnya dihabiskan untuk thrashing tata letak.

Masalah ini harus menjadi prioritas utama untuk diperbaiki.

Pendengar berulang

Secara terpisah, tidak ada yang istimewa dari 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 dieksekusi satu per satu. Listener adalah fungsi anonim (sehingga tidak ada yang dilaporkan di 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 digambar.

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

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

7. Penundaan input

Alasan umum terjadinya penundaan input—waktu sejak pengguna berinteraksi hingga saat pemroses peristiwa dapat mulai memproses interaksi—adalah karena thread utama sedang sibuk. Hal ini dapat disebabkan oleh beberapa hal:

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

Halaman demo memiliki fitur rahasia. Jika Anda mengklik logo siput di bagian atas halaman, logo tersebut akan mulai dianimasikan dan melakukan beberapa tugas JavaScript thread utama yang berat.

  • 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 pentalan dan lihat seberapa tinggi INP yang dapat Anda picu.

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

Di banyak halaman, beban kerja berat di thread utama tidak akan berperilaku sebaik ini, tetapi ini adalah demonstrasi yang baik untuk melihat bagaimana beban kerja berat dapat diidentifikasi dalam data atribusi INP.

Berikut contoh atribusi yang hanya berfokus pada 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—menunjukkan durasi pemrosesan 4,9 milidetik, dan sebagian besar interaksi yang buruk terjadi karena penundaan input, yang membutuhkan waktu 702,3 milidetik dari total 728.

Situasi ini sulit di-debug. Meskipun kita tahu apa yang berinteraksi dengan pengguna dan bagaimana, kita juga tahu bahwa bagian interaksi tersebut selesai dengan cepat dan tidak menjadi masalah. Namun, ada hal lain di halaman yang menunda dimulainya pemrosesan interaksi, tetapi bagaimana kita tahu harus mulai mencari dari mana?

Entri skrip LoAF hadir 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 cara pemicuan fungsi yang menunda pemrosesan interaksi (oleh pemroses animationiteration), fungsi mana yang bertanggung jawab, dan lokasinya dalam file sumber.

8. Penundaan presentasi: saat update tidak dapat ditampilkan

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

Muat ulang halaman untuk mereset nilai INP lagi, lalu buka menu hamburger. Ada jeda yang jelas saat aplikasi 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 menyebabkan sebagian besar interaksi 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

Menggabungkan data kolom

Perlu diketahui bahwa semua ini lebih mudah dilakukan saat melihat satu entri atribusi INP dari satu pemuatan halaman. Bagaimana data ini dapat digabungkan untuk men-debug INP berdasarkan data kolom? Jumlah detail yang berguna justru membuat hal ini lebih sulit.

Misalnya, sangat berguna untuk mengetahui elemen halaman mana yang menjadi sumber umum interaksi lambat. Namun, jika halaman Anda memiliki nama class CSS yang dikompilasi dan 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 apa yang paling berguna dan cara data dapat digabungkan. Misalnya, sebelum mengirimkan kembali data atribusi beacon, Anda dapat mengganti pemilih web-vitals dengan ID Anda sendiri, berdasarkan komponen 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 mengirimkan data kembali.

Sayangnya, tidak ada cara mudah untuk menangani data yang begitu kompleks ini, tetapi menggunakan sebagian data ini lebih berharga daripada tidak ada data atribusi sama sekali untuk proses penelusuran bug.

Atribusi di mana-mana!

Atribusi INP berbasis LoAF adalah bantuan pen-debug-an yang efektif. API ini menawarkan data terperinci tentang apa yang terjadi secara khusus selama INP. Dalam banyak kasus, laporan ini dapat menunjukkan 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