1. Pengantar
Ini adalah codelab interaktif untuk mempelajari cara mengukur Interaction to Next Paint (INP) menggunakan library web-vitals
.
Prasyarat
- Pengetahuan tentang pengembangan HTML dan JavaScript.
- Direkomendasikan: baca dokumentasi metrik INP web.dev.
Yang akan Anda pelajari
- Cara menambahkan library
web-vitals
ke halaman 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
.
- Clone repo di terminal Anda:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
. - Buka direktori yang di-clone:
cd web-vitals-codelabs/measuring-inp
. - Instal dependensi:
npm ci
. - Mulai server web:
npm run start
. - Buka http://localhost:8080/ di browser Anda.
Mencoba halaman
Codelab ini menggunakan Gastropodicon (situs referensi anatomi siput populer) untuk mempelajari potensi masalah dengan INP.
Coba berinteraksi dengan halaman untuk merasakan interaksi mana yang lambat.
3. Memahami 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, performanya kemungkinan akan jauh lebih baik daripada di perangkat seluler sungguhan. Untuk tampilan performa yang lebih realistis, klik roda gigi di kanan atas panel Performa, lalu pilih CPU 4x slowdown.
4. Menginstal metrik web
web-vitals
adalah library JavaScript untuk mengukur metrik Data Web yang dialami pengguna Anda. Anda dapat menggunakan library untuk mengambil nilai tersebut, lalu mengirimkannya ke endpoint analisis untuk analisis di lain waktu, untuk tujuan kami 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 untuk memeriksa dokumen library untuk mengetahui semua opsi Anda.
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:
- Build "standar" harus digunakan jika Anda ingin melacak nilai metrik Core Web Vitals saat pemuatan halaman.
- Build "atribusi" menambahkan informasi debug tambahan ke setiap metrik untuk mendiagnosis alasan metrik mendapatkan nilai yang ada.
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 saat konsol terbuka. Saat Anda mengklik halaman, tidak ada yang dicatat ke dalam log.
INP diukur selama seluruh siklus proses halaman, sehingga secara default, web-vitals
tidak melaporkan INP hingga pengguna keluar atau menutup halaman. Ini adalah perilaku ideal untuk beaconing untuk sesuatu seperti analisis, tetapi kurang ideal untuk proses debug secara interaktif.
web-vitals
menyediakan opsi reportAllChanges
untuk pelaporan yang lebih panjang. Jika diaktifkan, setiap interaksi tidak akan 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 akan diperbarui setiap kali ada interaksi terlama baru. Misalnya, coba ketik di kotak penelusuran, lalu hapus input.
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 sekarang dicatat ke konsol DevTools.
Informasi tingkat teratas 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 tampilan 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, kita paling tertarik dengan properti attribution
. Untuk membuat data atribusi, web-vitals
menemukan Frame Animasi Panjang (LoAF) yang tumpang-tindih dengan peristiwa klik. LoAF kemudian dapat memberikan data mendetail tentang 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 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 aktif ke elemen yang berinteraksi (jika elemen belum dihapus dari DOM).interactionTarget
: pemilih untuk menemukan elemen dalam halaman.
Selanjutnya, pengaturan waktu diuraikan 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, meskipun throttling CPU aktif.processingDuration
: waktu yang diperlukan pemroses peristiwa untuk berjalan hingga selesai. Sering kali, halaman akan memiliki beberapa pemroses untuk satu peristiwa (misalnya,pointerdown
,pointerup
, danclick
). Jika semuanya berjalan dalam frame animasi yang sama, pemroses tersebut 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 hal yang perlu dioptimalkan. Panduan Mengoptimalkan INP memiliki informasi selengkapnya tentang subjek ini.
Jika dipelajari lebih lanjut, 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 peristiwa ini menyertakan peristiwa lain seperti mouseover
dan mousedown
, bukan hanya peristiwa klik. Mengetahui peristiwa lain ini dapat sangat 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 menyebar 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 yang berguna di sini, seperti pengelompokan jumlah waktu yang dihabiskan untuk menata gaya. Artikel Long Animation Frames API membahas properti ini 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 sebagian besar dihabiskan dalam satu event-listener
, yang dipanggil di BUTTON#confirm.onclick
. Kita bahkan dapat melihat URL sumber skrip dan posisi karakter tempat fungsi didefinisikan.
Hasil
Apa yang dapat ditentukan tentang kasus ini dari data atribusi ini?
- Interaksi dipicu oleh klik pada elemen
button#confirm
(dariattribution.interactionTarget
dan propertiinvoker
pada entri atribusi skrip). - Waktu dihabiskan terutama untuk mengeksekusi pemroses peristiwa (dari
attribution.processingDuration
dibandingkan dengan total metrikvalue
). - Kode pemroses peristiwa lambat dimulai dari pemroses klik yang ditentukan di
third-party/cmp.js
(dariscripts.sourceURL
).
Data tersebut sudah cukup untuk mengetahui tempat yang perlu dioptimalkan.
6. Beberapa pemroses peristiwa
Muat ulang halaman agar konsol DevTools bersih dan interaksi izin cookie tidak lagi menjadi interaksi terpanjang.
Mulailah mengetik di kotak penelusuran. Apa yang ditunjukkan oleh 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 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
), 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 mengeksekusi pemroses peristiwa ini sebenarnya dihabiskan untuk thrashing tata letak.
Hal ini harus menjadi prioritas utama untuk diperbaiki.
Pemroses 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 tersebut adalah pemroses keyup
, yang dieksekusi satu per satu. Pemroses 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 apa pun dapat digambar.
Efek ini juga dapat berlipat ganda, semakin lama waktu yang diperlukan pemroses peristiwa untuk menyelesaikan, semakin banyak peristiwa input tambahan yang dapat masuk, sehingga memperpanjang interaksi lambat yang jauh lebih lama.
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 untuk penundaan input—waktu sejak pengguna berinteraksi hingga 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 sibuk melakukan pekerjaan awal untuk menyiapkan DOM, menata letak dan menata gaya halaman, serta mengevaluasi dan menjalankan skrip.
- Halaman biasanya sibuk—misalnya, menjalankan komputasi, animasi berbasis skrip, atau iklan.
- Interaksi sebelumnya memerlukan waktu yang sangat lama untuk diproses sehingga menunda interaksi mendatang yang terlihat pada contoh terakhir.
Halaman demo memiliki fitur rahasia. Jika Anda mengklik logo siput di bagian atas halaman, halaman akan mulai menganimasikan 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 halaman yang memicu 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 tepat saat siput memantul—pekerjaan thread utama akan menyebabkan halaman tidak responsif selama jangka 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 itu dapat diidentifikasi dalam data atribusi INP.
Berikut adalah contoh atribusi dari hanya memfokuskan kotak penelusuran selama pengalihan lambat:
{
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, yang memerlukan waktu 702,3 milidetik dari total 728.
Situasi ini dapat sulit di-debug. Meskipun kita tahu apa yang berinteraksi dengan pengguna dan caranya, kita juga tahu bahwa bagian interaksi tersebut selesai dengan cepat dan tidak menjadi masalah. Namun, ada hal lain di halaman yang menunda interaksi dari awal pemrosesan, tetapi bagaimana kita tahu tempat untuk mulai mencari?
Entri skrip LoAF hadir untuk menyelamatkan Anda:
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 dalam file sumber kita.
8. Penundaan presentasi: saat update tidak akan digambar
Penundaan presentasi mengukur waktu dari saat pemroses peristiwa selesai berjalan hingga browser dapat menggambar frame baru ke layar, yang menampilkan masukan yang terlihat oleh pengguna.
Muat ulang halaman untuk mereset nilai INP lagi, lalu buka menu tiga garis. Ada masalah yang jelas saat aplikasi dibuka.
Seperti apa tampilannya?
{
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 di user-callback
dari FrameRequestCallback
. Kali ini keterlambatan 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 lapangan? Jumlah detail yang bermanfaat justru mempersulit hal ini.
Misalnya, 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.
Sebagai gantinya, Anda harus memikirkan aplikasi tertentu untuk menentukan hal yang paling berguna dan cara data dapat digabungkan. Misalnya, sebelum mengirim kembali data atribusi beacon, 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 kembali data.
Sayangnya, tidak ada jalur mudah dengan data yang kompleks ini, tetapi bahkan menggunakan sebagian data tersebut lebih berharga daripada tidak ada data atribusi sama sekali untuk proses proses debug.
Atribusi di mana saja!
Atribusi INP berbasis LoAF adalah alat debug yang andal. Laporan ini menawarkan data terperinci tentang hal yang terjadi secara khusus selama INP. Dalam banyak kasus, laporan 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 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);