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

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

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

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

Browser Support

  • Chrome: 96.
  • Edge: 96.
  • Firefox Technology Preview: supported.
  • Safari: not supported.

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

ตั้งแต่เวอร์ชัน 4 ของไลบรารี web-vitals คุณจะได้รับข้อมูลเชิงลึกที่ละเอียดยิ่งขึ้นเกี่ยวกับการโต้ตอบที่มีปัญหาผ่านข้อมูลที่ได้จากการวิเคราะห์ระยะ 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 เมื่อเริ่มงานการแสดงผลสำหรับเฟรม ซึ่งรวมถึงการเรียกกลับ requestAnimationFrame (และ ResizeObserver หากมี) แต่ก่อนที่จะเริ่มงานด้านสไตล์/เลย์เอาต์
styleAndLayoutStart เมื่อมีการทำงานด้านสไตล์/เลย์เอาต์ในเฟรม อาจมีประโยชน์ในการพิจารณาความยาวของงานจัดรูปแบบ/เลย์เอาต์เมื่อพิจารณาการประทับเวลาอื่นๆ ที่มี
scripts อาร์เรย์ของรายการที่มีข้อมูลการระบุแหล่งที่มาของสคริปต์ซึ่งส่งผลต่อ INP ของหน้าเว็บ
ภาพการแสดงเฟรมของภาพเคลื่อนไหวที่ใช้เวลานานตามโมเดล LoAF
แผนภาพเวลาของเฟรมของภาพเคลื่อนไหวที่ใช้เวลานานตาม LoAF API (ลบ blockingDuration ออก)

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

คีย์ออบเจ็กต์การระบุแหล่งที่มาของสคริปต์ ข้อมูล
invoker ผู้เรียกใช้ ซึ่งอาจแตกต่างกันไปตามประเภทผู้เรียกใช้ที่อธิบายไว้ในแถวถัดไป ตัวอย่างของ Invoker อาจเป็นค่า เช่น 'IMG#id.onload', 'Window.requestAnimationFrame' หรือ 'Response.json.then'
invokerType ประเภทของ Invoker อาจเป็น '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 เหตุการณ์ที่ลงทะเบียน
  • ไฟล์สคริปต์และตำแหน่งอักขระภายในไฟล์ที่มีโค้ดตัวแฮนเดิลเหตุการณ์ที่ทำงานเป็นเวลานาน
  • ชื่อฟังก์ชัน

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

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

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

หากคุณบันทึกข้อมูลนี้ในฟิลด์และเห็นความล่าช้าในการป้อนข้อมูลสูงและประเภท Invoker เป็น '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.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 การเรียกกลับที่ใช้เวลานาน

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

การเรียกกลับเหล่านี้อาจใช้เวลานานพอสมควรจึงจะเสร็จสมบูรณ์หากงานที่ทำภายในมีความซับซ้อน หากสงสัยว่าค่าความล่าช้าในการนำเสนอสูงเกิดจากการทำงานร่วมกับ 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'
  }
});

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

บทสรุป

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

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