ค้นหาการโต้ตอบที่ช้าในช่อง

ดูวิธีค้นหาการโต้ตอบที่ช้าในข้อมูลภาคสนามของเว็บไซต์เพื่อให้คุณพบโอกาสในการปรับปรุง Interaction to Next Paint

ข้อมูลภาคสนามคือข้อมูลที่บอกให้ทราบว่าผู้ใช้จริงได้รับประสบการณ์อย่างไรจากเว็บไซต์ของคุณ ซึ่งจะช่วยให้คุณพบปัญหาที่หาไม่เจอในข้อมูลในห้องทดลองเพียงอย่างเดียว ในกรณีที่เกี่ยวข้องกับ Interaction to Next Paint (INP) ข้อมูลภาคสนามเป็นสิ่งสำคัญในการระบุการโต้ตอบที่ช้า และให้เบาะแสสำคัญที่จะช่วยคุณแก้ไขปัญหาได้

ในคู่มือนี้ คุณจะได้เรียนรู้วิธีประเมิน INP ของเว็บไซต์อย่างรวดเร็วโดยใช้ข้อมูลภาคสนามจากรายงานประสบการณ์ของผู้ใช้ Chrome (CrUX) เพื่อดูว่าเว็บไซต์มีปัญหาเกี่ยวกับ INP หรือไม่ จากนั้น คุณจะได้เรียนรู้วิธีใช้บิลด์การระบุแหล่งที่มาของไลบรารี JavaScript ของ web-vitals และข้อมูลเชิงลึกใหม่ๆ ที่ได้จาก Long Animation Frames API (LoAF) เพื่อรวบรวมและตีความข้อมูลภาคสนามสำหรับการโต้ตอบที่ช้าในเว็บไซต์

เริ่มต้นด้วย CrUX เพื่อประเมิน INP ของเว็บไซต์

หากคุณไม่ได้รวบรวมข้อมูลภาคสนามจากผู้ใช้เว็บไซต์ CrUX อาจเป็นจุดเริ่มต้นที่ดี CrUX จะรวบรวมข้อมูลภาคสนามจากผู้ใช้ Chrome จริงที่เลือกส่งข้อมูลการตรวจวัดระยะไกล

ข้อมูล CrUX จะแสดงในหลายๆ ส่วน และขึ้นอยู่กับขอบเขตของข้อมูลที่คุณกำลังมองหา CrUX สามารถให้ข้อมูลเกี่ยวกับ INP และ Core Web Vitals อื่นๆ สำหรับสิ่งต่อไปนี้

  • แต่ละหน้าและต้นทางทั้งหมดโดยใช้ PageSpeed Insights
  • ประเภทของหน้าเว็บ ตัวอย่างเช่น เว็บไซต์อีคอมเมิร์ซจำนวนมากมีประเภทหน้ารายละเอียดสินค้าและหน้าข้อมูลสินค้า คุณดูข้อมูล CrUX สำหรับหน้าเว็บประเภทต่างๆ ได้ใน Search Console

คุณสามารถป้อน URL ของเว็บไซต์ใน PageSpeed Insights เพื่อเริ่มต้นใช้งาน เมื่อป้อน URL แล้ว ข้อมูลภาคสนามของ URL ดังกล่าว (หากมี) จะแสดงสำหรับเมตริกหลายรายการ รวมถึง INP นอกจากนี้ คุณยังใช้ปุ่มเปิด/ปิดเพื่อตรวจสอบค่า INP สำหรับมิติข้อมูลบนอุปกรณ์เคลื่อนที่และเดสก์ท็อปได้ด้วย

ข้อมูลภาคสนามตามที่ CrUX แสดงใน PageSpeed Insights ซึ่งแสดง LCP, INP, CLS ที่ Core Web Vitals 3 รายการ และ TTFB, FCP เป็นเมตริกการวินิจฉัย และ FID เป็นเมตริก Core Web Vitals ที่เลิกใช้งานแล้ว
การอ่านข้อมูล CrUX ตามที่เห็นใน PageSpeed Insights ในตัวอย่างนี้ INP ของหน้าเว็บที่ระบุต้องได้รับการปรับปรุง

ข้อมูลนี้มีประโยชน์เนื่องจากจะบอกให้คุณทราบหากมีปัญหา อย่างไรก็ตาม CrUX ไม่สามารถบอกคุณได้ว่าอะไรเป็นสาเหตุของปัญหา มีโซลูชันการตรวจสอบผู้ใช้จริง (RUM) มากมายที่ช่วยให้คุณรวบรวมข้อมูลภาคสนามของคุณเองจากผู้ใช้เว็บไซต์เพื่อช่วยตอบคำถามดังกล่าว และตัวเลือกหนึ่งคือการรวบรวมข้อมูลภาคสนามด้วยตนเองโดยใช้ไลบรารี JavaScript ของ Web Vitals

รวบรวมข้อมูลภาคสนามด้วยไลบรารี JavaScript web-vitals

ไลบรารี JavaScript web-vitals เป็นสคริปต์ที่คุณโหลดในเว็บไซต์เพื่อรวบรวมข้อมูลภาคสนามจากผู้ใช้เว็บไซต์ได้ คุณสามารถใช้ไลบรารีนี้เพื่อบันทึกเมตริกต่างๆ รวมถึง INP ในเบราว์เซอร์ที่รองรับ

Browser Support

  • Chrome: 96.
  • Edge: 96.
  • Firefox: 144.
  • Safari: 26.2.

Source

คุณสามารถใช้บิลด์มาตรฐานของไลบรารี web-vitals เพื่อรับข้อมูล INP พื้นฐานจากผู้ใช้ในภาคสนามได้ ดังนี้

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  console.log(name);    // 'INP'
  console.log(value);   // 512
  console.log(rating);  // 'poor'
});

หากต้องการวิเคราะห์ข้อมูลภาคสนามจากผู้ใช้ คุณจะต้องส่งข้อมูลนี้ไปยังที่ใดที่หนึ่ง ดังนี้

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  // Prepare JSON to be sent for collection. Note that
  // you can add anything else you'd want to collect here:
  const body = JSON.stringify({name, value, rating});

  // Use `sendBeacon` to send data to an analytics endpoint.
  // For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
  navigator.sendBeacon('/analytics', body);
});

อย่างไรก็ตาม ข้อมูลนี้เพียงอย่างเดียวไม่ได้บอกอะไรคุณมากไปกว่าที่ CrUX บอก ซึ่งเป็นจุดที่บิลด์การระบุแหล่งที่มาของไลบรารี web-vitals เข้ามามีบทบาท

ก้าวไปอีกขั้นด้วยบิลด์การระบุแหล่งที่มาของไลบรารี web-vitals

บิลด์การระบุแหล่งที่มาของไลบรารี web-vitals จะแสดงข้อมูลเพิ่มเติมที่คุณได้รับจากผู้ใช้ในภาคสนาม เพื่อช่วยให้คุณแก้ปัญหาการโต้ตอบที่เป็นปัญหาซึ่งส่งผลต่อ INP ของเว็บไซต์ได้ดียิ่งขึ้น ข้อมูลนี้เข้าถึงได้ผ่านออบเจ็กต์ attribution ที่แสดงในเมธอด onINP() ของไลบรารี

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, rating, attribution}) => {
  console.log(name);         // 'INP'
  console.log(value);        // 56
  console.log(rating);       // 'good'
  console.log(attribution);  // Attribution data object
});
ลักษณะที่บันทึกคอนโซลจากไลบรารี web-vitals ปรากฏ คอนโซลในตัวอย่างนี้แสดงชื่อเมตริก (INP), ค่า INP (56), ตำแหน่งของค่าดังกล่าวภายในเกณฑ์ INP (ดี) และข้อมูลต่างๆ ที่แสดงในออบเจ็กต์การระบุแหล่งที่มา รวมถึงรายการจาก Long Animation Frames API
วิธีที่ข้อมูลจากไลบรารี web-vitals ปรากฏในคอนโซล

นอกเหนือจาก INP ของหน้าเว็บแล้ว บิลด์การระบุแหล่งที่มายังมีข้อมูลจำนวนมากที่คุณสามารถใช้เพื่อช่วยให้เข้าใจสาเหตุของการโต้ตอบที่ช้า รวมถึงส่วนของการโต้ตอบที่คุณควรให้ความสำคัญ ซึ่งจะช่วยตอบคำถามสำคัญ เช่น

  • "ผู้ใช้โต้ตอบกับหน้าเว็บขณะที่กำลังโหลดหรือไม่"
  • "ตัวแฮนเดิลเหตุการณ์ของการโต้ตอบทำงานเป็นเวลานานหรือไม่"
  • "โค้ดตัวแฮนเดิลเหตุการณ์การโต้ตอบเริ่มทำงานล่าช้าหรือไม่ หากใช่ มีอะไรเกิดขึ้นในเทรดหลัก ณ เวลานั้นอีกบ้าง"
  • "การโต้ตอบทำให้เกิดงานการแสดงผลจำนวนมากที่ทำให้เฟรมถัดไปแสดงผลล่าช้าหรือไม่"

ตารางต่อไปนี้แสดงข้อมูลการระบุแหล่งที่มาพื้นฐานบางอย่างที่คุณสามารถรับได้จากไลบรารี ซึ่งจะช่วยให้คุณทราบสาเหตุระดับสูงบางประการของการโต้ตอบที่ช้าในเว็บไซต์

คีย์ออบเจ็กต์ attribution รายการ ข้อมูล
interactionTarget ตัวเลือก CSS ที่ชี้ไปยังองค์ประกอบที่สร้างค่า INP ของหน้า เช่น button#save
interactionType ประเภทการโต้ตอบ ไม่ว่าจะเป็นการคลิก การแตะ หรือการป้อนข้อมูลด้วยแป้นพิมพ์
inputDelay* ความล่าช้าของอินพุตของการโต้ตอบ
processingDuration* เวลาตั้งแต่ที่ Listener เหตุการณ์แรกเริ่มทำงานเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้จนถึงเวลาที่การประมวลผล Listener เหตุการณ์ทั้งหมดเสร็จสิ้น
presentationDelay* ความล่าช้าในการแสดงผลของการโต้ตอบ ซึ่งเกิดขึ้นตั้งแต่ตอนที่ตัวแฮนเดิลเหตุการณ์ทำงานเสร็จจนถึงเวลาที่เฟรมถัดไปแสดงผล
longAnimationFrameEntries* รายการจาก LoAF ที่เชื่อมโยงกับการโต้ตอบ ดูข้อมูลเพิ่มเติมในส่วนถัดไป
*ใหม่ในเวอร์ชัน 4

ตั้งแต่ไลบรารี web-vitals เวอร์ชัน 4 เป็นต้นไป คุณจะได้รับข้อมูลเชิงลึกที่ละเอียดยิ่งขึ้นเกี่ยวกับการโต้ตอบที่เป็นปัญหาผ่านข้อมูลที่ไลบรารีนี้ให้มาพร้อมกับรายละเอียดระยะ INP (ความล่าช้าของอินพุต ระยะเวลาการประมวลผล และความล่าช้าในการนำเสนอ) และ Long Animation Frames API (LoAF)

Long Animation Frames API (LoAF)

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

การแก้ไขข้อบกพร่องของการโต้ตอบโดยใช้ข้อมูลภาคสนามเป็นงานที่ท้าทาย อย่างไรก็ตาม ข้อมูลจาก LoAF ช่วยให้คุณได้รับข้อมูลเชิงลึกที่ดีขึ้นเกี่ยวกับสาเหตุที่ทำให้การโต้ตอบช้า เนื่องจาก LoAF แสดงเวลาโดยละเอียดและข้อมูลอื่นๆ ที่คุณสามารถใช้เพื่อระบุสาเหตุที่แน่ชัด และที่สำคัญกว่านั้นคือแหล่งที่มาของปัญหาในโค้ดของเว็บไซต์

บิลด์การระบุแหล่งที่มาของไลบรารี web-vitals จะแสดงอาร์เรย์ของรายการ LoAF ภายใต้คีย์ longAnimationFrameEntries ของออบเจ็กต์ attribution ตารางต่อไปนี้แสดงข้อมูลสำคัญบางส่วนที่คุณจะพบในรายการ LoAF แต่ละรายการ

คีย์ออบเจ็กต์รายการ LoAF ข้อมูล
duration ระยะเวลาของเฟรมของภาพเคลื่อนไหวที่ใช้เวลานาน จนถึงเวลาที่เลย์เอาต์เสร็จสิ้น แต่ไม่รวมการวาดและการจัดองค์ประกอบ
blockingDuration ระยะเวลารวมในเฟรมที่เบราว์เซอร์ไม่สามารถตอบสนองได้อย่างรวดเร็วเนื่องจากงานที่ใช้เวลานาน เวลาในการบล็อกนี้อาจรวมถึงงานที่ใช้เวลานานซึ่งเรียกใช้ JavaScript รวมถึงงานการแสดงผลที่ใช้เวลานานในเฟรมที่ตามมา
firstUIEventTimestamp การประทับเวลาของเวลาที่เหตุการณ์อยู่ในคิวระหว่างเฟรม มีประโยชน์ในการระบุจุดเริ่มต้นของความล่าช้าของอินพุตของการโต้ตอบ
startTime การประทับเวลาเริ่มต้นของเฟรม
renderStart เมื่อเริ่มการแสดงผลเฟรม ซึ่งรวมถึง Callback requestAnimationFrame (และ Callback ResizeObserver หากมี) แต่อาจเกิดขึ้นก่อนที่การทำงานด้านสไตล์/เลย์เอาต์จะเริ่มต้นขึ้น
styleAndLayoutStart เมื่อมีการจัดสไตล์/เลย์เอาต์ในเฟรม ซึ่งอาจเป็นประโยชน์ในการพิจารณาความยาวของงานสไตล์/เลย์เอาต์เมื่อพิจารณาการประทับเวลาอื่นๆ ที่มีอยู่
scripts อาร์เรย์ของรายการที่มีข้อมูลการระบุแหล่งที่มาของสคริปต์ซึ่งส่งผลต่อ INP ของหน้าเว็บ
การแสดงภาพเฟรมของภาพเคลื่อนไหวที่ใช้เวลานานตามโมเดล LoAF
แผนภาพเวลาของเฟรมของภาพเคลื่อนไหวที่ใช้เวลานานตาม LoAF API (ลบ blockingDuration)

ข้อมูลทั้งหมดนี้บอกคุณได้มากมายเกี่ยวกับสาเหตุที่ทำให้การโต้ตอบช้า แต่คุณควรให้ความสนใจเป็นพิเศษกับอาร์เรย์ scripts ที่รายการ LoAF แสดงขึ้นมา

คีย์ออบเจ็กต์การระบุแหล่งที่มาของสคริปต์ ข้อมูล
invoker ผู้เรียกใช้ ซึ่งอาจแตกต่างกันไปตามประเภทผู้เรียกใช้ที่อธิบายไว้ในแถวถัดไป ตัวอย่างของ Invoker อาจเป็นค่าต่างๆ เช่น 'IMG#id.onload', 'Window.requestAnimationFrame' หรือ 'Response.json.then'
invokerType ประเภทของผู้เรียกใช้ อาจเป็น 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script' หรือ 'module-script'
sourceURL URL ไปยังสคริปต์ที่เป็นต้นกำเนิดของเฟรมของภาพเคลื่อนไหวที่ใช้เวลานาน
sourceCharPosition ตำแหน่งอักขระในสคริปต์ที่ระบุโดย sourceURL
sourceFunctionName ชื่อของฟังก์ชันในสคริปต์ที่ระบุ

แต่ละรายการในอาร์เรย์นี้มีข้อมูลที่แสดงในตารางนี้ ซึ่งจะให้ข้อมูลเกี่ยวกับสคริปต์ที่ทำให้เกิดการโต้ตอบที่ช้า และสาเหตุที่ทำให้เกิดการโต้ตอบที่ช้า

วัดและระบุสาเหตุที่พบบ่อยซึ่งทำให้การโต้ตอบช้า

เพื่อให้คุณเห็นภาพว่าจะใช้ข้อมูลนี้อย่างไร คู่มือนี้จะอธิบายวิธีใช้ข้อมูล LoAF ที่แสดงในไลบรารี web-vitals เพื่อระบุสาเหตุบางประการที่ทำให้การโต้ตอบช้า

ระยะเวลาในการประมวลผลที่ยาวนาน

ระยะเวลาการประมวลผลของการโต้ตอบคือเวลาที่ใช้ในการเรียกกลับของตัวแฮนเดิลเหตุการณ์ที่ลงทะเบียนไว้ของการโต้ตอบเพื่อเรียกใช้จนเสร็จสมบูรณ์ และสิ่งอื่นๆ ที่อาจเกิดขึ้นระหว่างนั้น ไลบรารี web-vitals จะแสดงระยะเวลาการประมวลผลที่สูง ดังนี้

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5
});

เป็นเรื่องปกติที่จะคิดว่าสาเหตุหลักที่ทำให้การโต้ตอบช้าคือโค้ดตัวแฮนเดิลเหตุการณ์ใช้เวลานานเกินไปในการทำงาน แต่นั่นก็ไม่ได้เป็นเช่นนั้นเสมอไป เมื่อยืนยันแล้วว่านี่คือปัญหา คุณสามารถเจาะลึกข้อมูล LoAF ได้ดังนี้

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5

  // Get the longest script from LoAF covering `processingDuration`:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Get attribution for the long-running event handler:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

ดังที่เห็นในข้อมูลโค้ดก่อนหน้านี้ คุณสามารถใช้ข้อมูล LoAF เพื่อติดตามสาเหตุที่แน่ชัดเบื้องหลังการโต้ตอบที่มีค่าระยะเวลาการประมวลผลสูง ซึ่งรวมถึงสิ่งต่อไปนี้

  • องค์ประกอบและ Listener เหตุการณ์ที่ลงทะเบียนไว้
  • ไฟล์สคริปต์และตำแหน่งอักขระภายในไฟล์ที่มีโค้ดตัวแฮนเดิลเหตุการณ์ที่ใช้เวลานาน
  • ชื่อของฟังก์ชัน

ข้อมูลประเภทนี้มีค่ามาก คุณไม่จำเป็นต้องทำงานหนักเพื่อค้นหาว่าการโต้ตอบใดหรือตัวแฮนเดิลเหตุการณ์ใดที่ทำให้ค่าระยะเวลาการประมวลผลสูงอีกต่อไป นอกจากนี้ เนื่องจากสคริปต์ของบุคคลที่สามมักจะลงทะเบียนเครื่องมือจัดการเหตุการณ์ของตนเองได้ คุณจึงระบุได้ว่าโค้ดของคุณเป็นสาเหตุหรือไม่ สำหรับโค้ดที่คุณควบคุมได้ คุณจะต้องพิจารณาการเพิ่มประสิทธิภาพงานที่ใช้เวลานาน

ความล่าช้าของอินพุตที่นาน

แม้ว่าตัวแฮนเดิลเหตุการณ์ที่ทำงานเป็นเวลานานจะเป็นเรื่องปกติ แต่ก็ยังมีส่วนอื่นๆ ของการโต้ตอบที่ต้องพิจารณา ส่วนแรกจะเกิดขึ้นก่อนระยะเวลาการประมวลผล ซึ่งเรียกว่าความล่าช้าของอินพุต ซึ่งเป็นระยะเวลาตั้งแต่ที่ผู้ใช้เริ่มการโต้ตอบจนถึงช่วงเวลาที่ Callback ของตัวแฮนเดิลเหตุการณ์เริ่มทำงาน และเกิดขึ้นเมื่อเทรดหลักกำลังประมวลผลงานอื่นอยู่แล้ว บิลด์การระบุแหล่งที่มาของไลบรารี web-vitals จะบอกระยะเวลาของความล่าช้าของอินพุตสำหรับการโต้ตอบได้ ดังนี้

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536
});

หากสังเกตเห็นว่าการโต้ตอบบางอย่างมีความล่าช้าในการป้อนข้อมูลสูง คุณจะต้องค้นหาสิ่งที่เกิดขึ้นในหน้าเว็บ ณ เวลาที่มีการโต้ตอบซึ่งทำให้เกิดความล่าช้าในการป้อนข้อมูลเป็นเวลานาน และมักจะสรุปได้ว่าการโต้ตอบเกิดขึ้นขณะที่หน้าเว็บกำลังโหลดหรือหลังจากนั้น

เกิดขึ้นระหว่างการโหลดหน้าเว็บใช่ไหม

เทรดหลักมักจะทำงานหนักที่สุดขณะที่หน้าเว็บกำลังโหลด ในช่วงเวลานี้ งานทุกประเภทจะอยู่ในคิวและได้รับการประมวลผล และหากผู้ใช้พยายามโต้ตอบกับหน้าเว็บขณะที่งานทั้งหมดนี้กำลังดำเนินอยู่ ก็อาจทำให้การโต้ตอบล่าช้า หน้าเว็บที่โหลด JavaScript จำนวนมากสามารถเริ่มการทำงานเพื่อคอมไพล์และประเมินสคริปต์ รวมถึงเรียกใช้ฟังก์ชันที่ทำให้หน้าเว็บพร้อมสำหรับการโต้ตอบของผู้ใช้ งานนี้อาจขัดขวางได้หากผู้ใช้โต้ตอบขณะที่กิจกรรมนี้เกิดขึ้น และคุณสามารถดูได้ว่ากรณีนี้เกิดขึ้นกับผู้ใช้เว็บไซต์ของคุณหรือไม่โดยทำดังนี้

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    // Invoker types can describe if script eval blocked the main thread:
    const {invokerType} = script;    // 'classic-script' | 'module-script'
    const {sourceLocation} = script; // 'https://example.com/app.js'
  }
});

หากคุณบันทึกข้อมูลนี้ในช่องและเห็นความล่าช้าในการป้อนข้อมูลสูงและประเภทผู้เรียกใช้เป็น 'classic-script' หรือ 'module-script' ก็ถือว่าสคริปต์ในเว็บไซต์ใช้เวลานานในการประเมิน และบล็อกเทรดหลักนานพอที่จะทำให้การโต้ตอบล่าช้า คุณลดเวลาการบล็อกนี้ได้โดยการแบ่งสคริปต์ออกเป็นกลุ่มย่อยๆ เลื่อนเวลาโหลดโค้ดที่ไม่ได้ใช้ในตอนแรกออกไป และตรวจสอบเว็บไซต์เพื่อหาโค้ดที่ไม่ได้ใช้ซึ่งคุณสามารถนำออกได้ทั้งหมด

หลังจากโหลดหน้าเว็บใช่ไหม

แม้ว่าความล่าช้าในการป้อนข้อมูลมักเกิดขึ้นขณะที่หน้าเว็บกำลังโหลด แต่ก็อาจเกิดขึ้นหลังจากที่หน้าเว็บโหลดแล้วได้เช่นกัน เนื่องจากสาเหตุที่แตกต่างกันโดยสิ้นเชิง สาเหตุที่พบบ่อยของความล่าช้าในการป้อนข้อมูลหลังจากการโหลดหน้าเว็บอาจเป็นโค้ดที่ทำงานเป็นระยะๆ เนื่องจากการเรียก setInterval ก่อนหน้านี้ หรือแม้แต่ Callback ของเหตุการณ์ที่อยู่ในคิวให้ทำงานก่อนหน้านี้และยังคงประมวลผลอยู่

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536

  // Get the longest script from the first LoAF entry:
  const loaf = attribution.longAnimationFrameEntries[0];
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  if (script) {
    const {invokerType} = script;        // 'user-callback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

เช่นเดียวกับการแก้ปัญหาค่าระยะเวลาการประมวลผลสูง ความล่าช้าในการป้อนข้อมูลสูงเนื่องจากสาเหตุที่กล่าวถึงก่อนหน้านี้จะให้ข้อมูลการระบุแหล่งที่มาของสคริปต์โดยละเอียดแก่คุณ อย่างไรก็ตาม สิ่งที่แตกต่างคือประเภทผู้เรียกจะเปลี่ยนไปตามลักษณะของงานที่ทำให้การโต้ตอบล่าช้า ดังนี้

  • 'user-callback' บ่งบอกว่างานที่บล็อกมาจาก setInterval, setTimeout หรือแม้แต่ requestAnimationFrame
  • 'event-listener' บ่งชี้ว่างานที่บล็อกมาจากอินพุตก่อนหน้าที่อยู่ในคิวและยังคงประมวลผลอยู่
  • 'resolve-promise' และ 'reject-promise' หมายความว่างานที่บล็อกมาจากงานแบบอะซิงโครนัสบางอย่างที่เริ่มทำงานก่อนหน้านี้ และได้รับการแก้ไขหรือปฏิเสธในเวลาที่ผู้ใช้พยายามโต้ตอบกับหน้าเว็บ ซึ่งทำให้การโต้ตอบล่าช้า

ไม่ว่าในกรณีใด ข้อมูลการระบุแหล่งที่มาของสคริปต์จะช่วยให้คุณทราบว่าจะเริ่มดูจากจุดใด และความล่าช้าในการป้อนข้อมูลเกิดจากโค้ดของคุณเองหรือสคริปต์ของบุคคลที่สาม

ความล่าช้าของงานนำเสนอที่ยาวนาน

ความล่าช้าในการนำเสนอเป็นขั้นตอนสุดท้ายของการโต้ตอบ และจะเริ่มขึ้นเมื่อตัวแฮนเดิลเหตุการณ์ของการโต้ตอบเสร็จสิ้น จนถึงจุดที่วาดเฟรมถัดไป ซึ่งจะเกิดขึ้นเมื่อการทำงานในเครื่องจัดการเหตุการณ์เนื่องจากการโต้ตอบเปลี่ยนสถานะภาพของอินเทอร์เฟซผู้ใช้ เช่นเดียวกับระยะเวลาการประมวลผลและความล่าช้าของอินพุต ไลบรารี web-vitals จะบอกได้ว่าความล่าช้าในการนำเสนอสำหรับการโต้ตอบนั้นนานเท่าใด

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691
});

หากคุณบันทึกข้อมูลนี้และเห็นความล่าช้าในการนำเสนอสูงสำหรับการโต้ตอบที่ส่งผลต่อ INP ของเว็บไซต์ สาเหตุอาจแตกต่างกันไป แต่สาเหตุที่ควรระวังมีดังนี้

การจัดสไตล์และเลย์เอาต์ที่มีค่าใช้จ่ายสูง

ความล่าช้าในการแสดงผลที่นานอาจเกิดจากการคำนวณสไตล์ใหม่และงานเลย์เอาต์ที่มีค่าใช้จ่ายสูง ซึ่งเกิดจากสาเหตุหลายประการ เช่น ตัวเลือก CSS ที่ซับซ้อนและ DOM ขนาดใหญ่ คุณวัดระยะเวลาของงานนี้ได้ด้วยการจับเวลา LoAF ที่แสดงในไลบรารี web-vitals ดังนี้

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  // Get necessary timings:
  const {startTime} = loaf; // 2120.5
  const {duration} = loaf;  // 1002

  // Figure out the ending timestamp of the frame (approximate):
  const endTime = startTime + duration; // 3122.5

  // Get the start timestamp of the frame's style/layout work:
  const {styleAndLayoutStart} = loaf; // 3011.17692309

  // Calculate the total style/layout duration:
  const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running style and layout operation:
    const {invokerType} = script;        // 'event-listener'
    const {invoker} = script;            // 'BUTTON#update.onclick'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

LoAF จะไม่บอกระยะเวลาของงานสไตล์และเลย์เอาต์สำหรับเฟรม แต่จะบอกเวลาที่เริ่ม เมื่อใช้การประทับเวลาเริ่มต้นนี้ คุณจะใช้ข้อมูลอื่นๆ จาก LoAF เพื่อคำนวณระยะเวลาที่แน่นอนของงานนั้นได้โดยการกำหนดเวลาสิ้นสุดของเฟรม แล้วลบการประทับเวลาเริ่มต้นของงานสไตล์และเลย์เอาต์ออกจากค่านั้น

การเรียกกลับ requestAnimationFrame ที่ใช้เวลานาน

สาเหตุหนึ่งที่อาจทำให้เกิดความล่าช้าในการนำเสนอเป็นเวลานานคือการทำงานมากเกินไปใน Callback requestAnimationFrame เนื้อหาของ Callback นี้จะทำงานหลังจากที่ตัวแฮนเดิลเหตุการณ์ทำงานเสร็จแล้ว แต่จะทำงานก่อนการคำนวณสไตล์และเลย์เอาต์ใหม่

การเรียกกลับเหล่านี้อาจใช้เวลาดำเนินการนานหากงานที่ทำภายในมีความซับซ้อน หากสงสัยว่าค่าความล่าช้าในการนำเสนอสูงเกิดจากงานที่คุณทำกับ requestAnimationFrame คุณสามารถใช้ข้อมูล LoAF ที่แสดงโดยไลบรารี web-vitals เพื่อระบุสถานการณ์ต่อไปนี้ได้

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 543.1999999880791

  // Get the longest script from the last LoAF entry:
  const loaf = attribution.longAnimationFrameEntries.at(-1);
  const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];

  // Get the render start time and when style and layout began:
  const {renderStart} = loaf;         // 2489
  const {styleAndLayoutStart} = loaf; // 2989.5999999940395

  // Calculate the `requestAnimationFrame` callback's duration:
  const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954

  if (script) {
    // Get attribution for the event handler that triggered
    // the long-running requestAnimationFrame callback:
    const {invokerType} = script;        // 'user-callback'
    const {invoker} = script;            // 'FrameRequestCallback'
    const {sourceURL} = script;          // 'https://example.com/app.js'
    const {sourceCharPosition} = script; // 83
    const {sourceFunctionName} = script; // 'update'
  }
});

หากคุณเห็นว่าเวลาส่วนใหญ่ของความล่าช้าในการนำเสนอใช้ไปกับ Callback requestAnimationFrame ให้ตรวจสอบว่างานที่คุณทำใน Callback เหล่านี้จำกัดเฉพาะงานที่ส่งผลให้เกิดการอัปเดตอินเทอร์เฟซผู้ใช้จริง งานอื่นๆ ที่ไม่ได้แตะ DOM หรืออัปเดตสไตล์จะทำให้เฟรมถัดไปแสดงผลล่าช้าโดยไม่จำเป็น ดังนั้นโปรดระมัดระวัง

บทสรุป

ข้อมูลภาคสนามเป็นแหล่งข้อมูลที่ดีที่สุดที่คุณสามารถใช้เพื่อทำความเข้าใจว่าการโต้ตอบใดเป็นปัญหาสำหรับผู้ใช้จริงในภาคสนาม การใช้เครื่องมือเก็บรวบรวมข้อมูลภาคสนาม เช่น ไลบรารี JavaScript ของ web-vitals (หรือผู้ให้บริการ RUM) จะช่วยให้คุณมั่นใจมากขึ้นว่าการโต้ตอบใดมีปัญหามากที่สุด จากนั้นจึงจำลองการโต้ตอบที่มีปัญหาในห้องทดลองเพื่อแก้ไขปัญหาต่อไป