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

ดูวิธีค้นหาการโต้ตอบที่ช้าในข้อมูลภาคสนามของเว็บไซต์เพื่อให้พบโอกาสในการปรับปรุงการโต้ตอบกับ 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 สำหรับการอัปโหลดบนเว็บ

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

นอกเหนือจาก 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)

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

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

แหล่งที่มา

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

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

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

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

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 ที่แสดงในไลบรารีไฟล์วิดีโอบนเว็บ

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 ที่ทำงานอยู่นาน

สาเหตุหนึ่งที่อาจทําให้เกิดความล่าช้าในการนำเสนอเป็นเวลานานคือมีการทำงานมากเกินไปใน 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