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

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

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

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

การรองรับเบราว์เซอร์

  • Chrome: 96
  • Edge: 96
  • Firefox: ไม่รองรับ
  • Safari: ไม่รองรับ

แหล่งที่มา

คุณสามารถใช้บิลด์มาตรฐานของไลบรารี 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 (ดี) และข้อมูลต่างๆ ที่แสดงในออบเจ็กต์การระบุแหล่งที่มา รวมถึงรายการจาก The Long Animation Frames API
ลักษณะที่ข้อมูลจากคลัง Web Vitals ปรากฏในคอนโซล

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

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

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

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

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

Long Animation Frames API (LoAF)

การรองรับเบราว์เซอร์

  • Chrome: 123
  • Edge: 123
  • Firefox: ไม่รองรับ
  • Safari: ไม่รองรับ

แหล่งที่มา

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

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

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

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

คีย์ออบเจ็กต์การระบุแหล่งที่มาของสคริปต์ ข้อมูล
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.sort((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 เหตุการณ์ที่ลงทะเบียนไว้
  • ไฟล์สคริปต์และตําแหน่งของอักขระภายในไฟล์ซึ่งมีโค้ดตัวแฮนเดิลเหตุการณ์ที่ทำงานอยู่นาน
  • ชื่อของฟังก์ชัน

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

ความล่าช้าในการป้อนข้อมูลนาน

แม้ว่าตัวแฮนเดิลเหตุการณ์ที่ทำงานอยู่นานจะพบได้ทั่วไป แต่ก็มีบางส่วนของการโต้ตอบที่ต้องพิจารณาด้วย ส่วนหนึ่งเกิดขึ้นก่อนระยะเวลาการประมวลผล ซึ่งเรียกว่าการหน่วงเวลาอินพุต ระยะเวลานี้นับตั้งแต่ที่ผู้ใช้เริ่มการโต้ตอบจนถึงเวลาที่การเรียกกลับของตัวแฮนเดิลเหตุการณ์เริ่มทํางาน และเกิดขึ้นเมื่อเธรดหลักประมวลผลงานอื่นอยู่แล้ว บิลด์การระบุแหล่งที่มาของไลบรารี 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.sort((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 ก่อนหน้านี้ หรือแม้แต่การเรียกเหตุการณ์กลับที่รอคิวให้ทํางานก่อนหน้านี้และยังประมวลผลอยู่

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.sort((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 ของเว็บไซต์ สาเหตุอาจแตกต่างกันไป แต่สาเหตุ 2 ข้อที่ควรระวังมีดังนี้

การออกแบบและเลย์เอาต์ที่ราคาแพง

ความล่าช้าในการนำเสนอที่นานอาจเกิดจากการคำนวณสไตล์ใหม่และเลย์เอาต์ที่มีค่าใช้จ่ายสูง ซึ่งอาจเกิดจากสาเหตุหลายประการ เช่น เครื่องมือเลือก 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.sort((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.sort((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'
  }
});

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

บทสรุป

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

รูปภาพหลักจาก Unsplash โดย Federico Respini