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 dengan kemampuan untuk 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 ditemukan di repositori web-vitals-codelabs.

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

Coba halaman ini

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

Screenshot halaman demo Gastropodicon

Coba berinteraksi dengan halaman untuk mengetahui interaksi mana yang lambat.

3. Memasang di Chrome DevTools

Buka DevTools dari Alat Lainnya > Menu Developer Tools, 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 tab ini 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 akan jauh lebih baik daripada di perangkat seluler sungguhan. Untuk tampilan performa yang lebih realistis, tekan roda gigi di kanan atas panel Performance, lalu pilih CPU 4x pelambatan.

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

4. Menginstal web-vital

web-vitals adalah library JavaScript untuk mengukur metrik Web Vitals yang dialami pengguna. Anda dapat menggunakan library untuk menangkap nilai tersebut, lalu beacon ke endpoint analisis untuk analisis di lain waktu, agar kita dapat mengetahui kapan dan di mana interaksi lambat terjadi.

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

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

Ada dua versi web-vitals yang dapat Anda gunakan:

  • "Standar" yang harus digunakan jika Anda ingin melacak nilai metrik Core Web Vitals pada pemuatan halaman.
  • "Atribusi" {i>build <i}akan menambahkan informasi debug tambahan ke setiap metrik untuk mendiagnosis penyebab suatu metrik memiliki nilai yang sama.

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

INP diukur di seluruh siklus proses halaman, sehingga secara default, web-vitals tidak melaporkan INP hingga pengguna meninggalkan atau menutup halaman. Ini merupakan perilaku ideal bagi beaconing untuk hal seperti analisis, tetapi kurang ideal untuk proses debug secara interaktif.

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

Coba tambahkan opsi ke skrip dan berinteraksi lagi dengan halaman:

<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 mengetik di kotak penelusuran, lalu menghapus 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 di halaman tersebut, yaitu dialog izin cookie.

Banyak halaman akan memiliki skrip yang memerlukan cookie yang dipicu secara sinkron saat cookie diterima oleh pengguna, yang 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 sampai cat berikutnya adalah 344 milidetik—"perlu peningkatan" INP Anda. 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 periode ini, kita ingin mempelajari properti attribution. Untuk membuat data atribusi, web-vitals menemukan Bingkai Animasi Panjang (LoAF) mana yang tumpang-tindih dengan peristiwa klik. LoAF kemudian dapat memberikan data mendetail tentang cara waktu yang dihabiskan selama frame tersebut, mulai dari skrip yang berjalan, hingga waktu yang dihabiskan dalam callback, gaya, dan tata letak requestAnimationFrame.

Luaskan properti attribution untuk melihat informasi selengkapnya. Datanya jauh lebih kaya.

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

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

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

Pertama, ada informasi tentang objek yang menerima interaksi:

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

Selanjutnya, pengaturan waktu tersebut diperinci secara garis besar:

  • 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 saat throttling CPU diaktifkan.
  • processingDuration: waktu yang diperlukan pemroses peristiwa untuk dijalankan hingga selesai. Sering kali, halaman akan memiliki beberapa pemroses untuk satu peristiwa (misalnya, pointerdown, pointerup, dan click). Jika semuanya berjalan dalam {i>frame<i} animasi yang sama, semua elemen tersebut akan digabungkan ke dalam {i>frame<i} ini. Dalam hal ini, durasi pemrosesan memerlukan 295,6 milidetik, yang merupakan sebagian besar waktu INP.
  • presentationDelay: waktu dari saat pemroses peristiwa selesai hingga browser selesai menggambar frame berikutnya. Dalam hal ini, 21,4 milidetik.

Fase INP ini dapat menjadi sinyal penting untuk mendiagnosis hal yang perlu dioptimalkan. Panduan Optimize INP berisi informasi selengkapnya mengenai hal ini.

Lebih dalam lagi, processedEventEntries berisi lima peristiwa, bukan satu peristiwa dalam array entries INP 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, dalam hal ini klik. Atribusi processedEventEntries adalah semua peristiwa yang diproses selama frame yang sama. Perhatikan bahwa ini mencakup peristiwa lain seperti mouseover dan mousedown, bukan hanya peristiwa klik. Mengetahui tentang peristiwa-peristiwa lain ini bisa sangat penting jika peristiwa-peristiwa tersebut juga lambat, karena semuanya berkontribusi pada respons yang lambat.

Terakhir adalah array longAnimationFrameEntries. Entri ini dapat berupa satu entri, tetapi ada kasus saat interaksi dapat menyebar di beberapa frame. Di sini kita memiliki kasus paling sederhana dengan satu bingkai 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 yang berguna di sini, seperti menguraikan jumlah waktu yang dihabiskan untuk menata gaya. Artikel Long Animation Frames API membahas properti ini secara lebih mendalam. Saat ini, kita terutama tertarik pada 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 pada 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 di entri atribusi skrip).
  • Waktu yang dihabiskan terutama untuk mengeksekusi pemroses peristiwa (dari attribution.processingDuration dibandingkan dengan total metrik value).
  • Kode pemroses peristiwa lambat dimulai dari pemroses klik yang ditentukan dalam third-party/cmp.js (dari scripts.sourceURL).

Jumlah data yang cukup untuk mengetahui area yang perlu dioptimalkan.

6. Beberapa pemroses peristiwa

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

Mulai ketikkan di kotak penelusuran. Apa yang ditunjukkan oleh 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 1.072 milidetik INP—dihabiskan dalam durasi pemrosesan.

Namun, entri scripts lebih menarik.

Thrashing tata letak

Entri pertama array scripts memberi kita beberapa konteks 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 saat browser terpaksa menata ulang halaman. Dengan kata lain, 78% dari waktu—388 milidetik dari 497—yang dihabiskan untuk mengeksekusi pemroses peristiwa ini sebenarnya dihabiskan dalam layout thrashing.

Masalah ini harus menjadi prioritas utama untuk diperbaiki.

Pemroses berulang

Secara individu, tidak ada yang luar biasa 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 mengeksekusi satu per satu. Pemroses adalah fungsi anonim (jadi tidak ada yang dilaporkan di properti sourceFunctionName), tetapi kita masih memiliki file sumber dan posisi karakter sehingga kita dapat menemukan lokasi kode.

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

Browser akhirnya memproses beberapa penekanan tombol dalam satu bingkai animasi, yang menyebabkan pemroses peristiwa ini berjalan dua kali sebelum apa pun dapat digambar.

Efek ini juga dapat digabungkan, di mana semakin lama waktu yang dibutuhkan pemroses peristiwa untuk menyelesaikan, semakin banyak peristiwa input tambahan yang bisa masuk, sehingga memperpanjang interaksi lambat yang lebih lama.

Karena ini adalah interaksi penelusuran/pelengkapan otomatis, menghapuskan input akan menjadi strategi yang baik sehingga maksimal satu penekanan tombol akan 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 disebabkan oleh beberapa penyebab:

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

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

  • Klik logo siput untuk memulai animasi.
  • Tugas JavaScript dipicu saat siput berada di bagian bawah pantulan. Cobalah untuk 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 dengan mengklik dan memfokuskan kotak penelusuran tepat saat siput memantul—pekerjaan utas utama akan menyebabkan halaman tidak responsif untuk waktu yang cukup lama.

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

Berikut ini contoh atribusi dengan hanya memfokuskan kotak penelusuran saat siput memantul:

{
  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 dihabiskan dalam penundaan input, sehingga mengambil 702,3 milidetik dari total 728.

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

Entri skrip LoAF siap untuk membantu:

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 tidak ada hubungannya dengan interaksi, fungsi ini memperlambat bingkai animasi, sehingga termasuk 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 update gagal ditampilkan

Penundaan presentasi mengukur waktu dari saat pemroses peristiwa selesai berjalan hingga browser dapat menggambar frame baru ke layar, yang menunjukkan masukan yang terlihat kepada pengguna.

Muat ulang halaman untuk mereset nilai INP lagi, lalu buka menu tiga garis. Ada halangan pasti saat {i>open<i} 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 adalah penundaan presentasi yang mengisi sebagian besar interaksi lambat. Itu berarti apa pun yang memblokir thread utama akan 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,
}]

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

9. Kesimpulan

Menggabungkan data kolom

Perlu diketahui bahwa ini semua lebih mudah jika melihat satu entri atribusi INP dari pemuatan satu halaman. Bagaimana cara data ini dapat digabungkan untuk men-debug INP berdasarkan data kolom? Jumlah detail yang membantu sebenarnya membuat proses ini lebih sulit.

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

Sebaliknya, Anda harus memikirkan aplikasi tertentu untuk menentukan apa yang paling berguna dan bagaimana data dapat digabungkan. Misalnya, sebelum data atribusi beaconing 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 mengubah data kembali.

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

Atribusi di mana saja.

Atribusi INP berbasis LoAF merupakan bantuan proses debug yang canggih. Laporan ini menawarkan data terperinci tentang hal-hal yang secara spesifik terjadi selama INP. Dalam banyak kasus, fitur ini dapat mengarahkan Anda ke lokasi akurat dalam skrip tempat Anda harus memulai upaya pengoptimalan.

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

Meskipun tidak memiliki akses untuk mengedit halaman, Anda bisa 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