API วงจรการใช้งานหน้าเว็บ

Browser Support

  • Chrome: 68.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

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

ฉากหลัง

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

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

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

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

ปัญหาคือ นักพัฒนาแอปไม่มีวิธีเตรียมพร้อมรับการแทรกแซงประเภทนี้ที่ระบบเป็นผู้เริ่ม หรือแม้แต่ทราบว่าการแทรกแซงเกิดขึ้น ซึ่งหมายความว่าเบราว์เซอร์ต้องระมัดระวังหรือเสี่ยงต่อการทำให้หน้าเว็บเสียหาย

Page Lifecycle API พยายามแก้ปัญหานี้โดยทำดังนี้

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

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

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

ภาพรวมของสถานะและเหตุการณ์ในวงจรของหน้าเว็บ

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

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

ภาพแสดงสถานะและโฟลว์เหตุการณ์ที่อธิบายไว้ตลอดทั้งเอกสารนี้
สถานะ API วงจรหน้าเว็บและโฟลว์เหตุการณ์

รัฐ

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

รัฐ คำอธิบาย
ใช้งานอยู่

หน้าเว็บจะอยู่ในสถานะใช้งานอยู่หากมองเห็นได้และมี โฟกัสอินพุต

สถานะก่อนหน้าที่เป็นไปได้
passive (ผ่านเหตุการณ์ focus)
frozen (ผ่านเหตุการณ์ resume จากนั้นเหตุการณ์ pageshow)

สถานะถัดไปที่เป็นไปได้
passive (ผ่านเหตุการณ์ blur)

เชิงรับ

หน้าเว็บจะอยู่ในสถานะพาสซีฟหากมองเห็นได้และไม่มีโฟกัสอินพุต

สถานะก่อนหน้าที่เป็นไปได้
ใช้งานอยู่ (ผ่านเหตุการณ์ blur)
ซ่อน (ผ่านเหตุการณ์ visibilitychange)
หยุดชั่วคราว (ผ่านเหตุการณ์ resume จากนั้นเหตุการณ์ pageshow)

สถานะถัดไปที่เป็นไปได้
active (ผ่านเหตุการณ์ focus)
hidden (ผ่านเหตุการณ์ visibilitychange)

ซ่อน

หน้าเว็บจะอยู่ในสถานะซ่อนหากมองไม่เห็น (และยังไม่ได้ ถูกตรึง ถูกทิ้ง หรือถูกปิด)

สถานะก่อนหน้าที่เป็นไปได้
passive (ผ่านเหตุการณ์ visibilitychange)
frozen (ผ่านเหตุการณ์ resume จากนั้นเหตุการณ์ pageshow)

สถานะถัดไปที่เป็นไปได้
passive (ผ่านเหตุการณ์ visibilitychange)
frozen (ผ่านเหตุการณ์ freeze)
discarded (ไม่มีการทริกเกอร์เหตุการณ์)
terminated (ไม่มีการทริกเกอร์เหตุการณ์)

ค้าง

ในสถานะหยุดชั่วคราว เบราว์เซอร์จะระงับการดำเนินการของ freezable tasks ใน คิวของงานของหน้าเว็บจนกว่าจะยกเลิกการหยุดชั่วคราวของหน้าเว็บ ซึ่งหมายความว่าสิ่งต่างๆ เช่น ตัวจับเวลา JavaScript และการเรียกกลับของฟีเจอร์ Fetch จะไม่ทำงาน งานที่กำลังทำงานอยู่ จะทำงานจนเสร็จได้ (ที่สำคัญที่สุดคือการเรียกกลับ freeze) แต่การทำงานอาจถูกจำกัด และระยะเวลาการทำงานอาจถูกจำกัด

เบราว์เซอร์จะหยุดการทำงานของหน้าเว็บเพื่อรักษาการใช้งาน CPU/แบตเตอรี่/อินเทอร์เน็ต และยังทำเพื่อช่วยให้ การไปยังหน้าก่อนหน้า/ถัดไปเร็วขึ้นด้วย ซึ่งจะช่วยให้ไม่ต้องโหลดหน้าเว็บซ้ำทั้งหมด

สถานะก่อนหน้าที่เป็นไปได้
ซ่อน (ผ่านเหตุการณ์ freeze)

สถานะถัดไปที่เป็นไปได้
active (ผ่านเหตุการณ์ resume จากนั้นเหตุการณ์ pageshow)
passive (ผ่านเหตุการณ์ resume จากนั้นเหตุการณ์ pageshow)
hidden (ผ่านเหตุการณ์ resume)
discarded (ไม่มีการทริกเกอร์เหตุการณ์)

สิ้นสุด

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

สถานะก่อนหน้าที่เป็นไปได้
ซ่อน (ผ่านเหตุการณ์ pagehide)

สถานะถัดไปที่เป็นไปได้:
ไม่มี

ทิ้ง

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

ในสถานะทิ้ง โดยปกติแล้วผู้ใช้จะเห็นแท็บเอง (รวมถึงชื่อแท็บและ favicon) แม้ว่าหน้าจะหายไปแล้วก็ตาม

สถานะก่อนหน้าที่เป็นไปได้:
ซ่อน (ไม่มีการทริกเกอร์เหตุการณ์)
หยุดชั่วคราว (ไม่มีการทริกเกอร์เหตุการณ์)

สถานะถัดไปที่เป็นไปได้:
ไม่มี

กิจกรรม

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

ชื่อ รายละเอียด
focus

องค์ประกอบ DOM ได้รับโฟกัส

หมายเหตุ: เหตุการณ์ focus ไม่ได้ จำเป็นต้องส่งสัญญาณการเปลี่ยนแปลงสถานะ โดยจะส่งสัญญาณการเปลี่ยนแปลงสถานะก็ต่อเมื่อ ก่อนหน้านี้หน้าเว็บไม่มีโฟกัสอินพุต

สถานะก่อนหน้าที่เป็นไปได้:
passive

สถานะปัจจุบันที่เป็นไปได้:
active

blur

องค์ประกอบ DOM สูญเสียโฟกัส

หมายเหตุ: เหตุการณ์ blur ไม่ได้ จำเป็นต้องส่งสัญญาณการเปลี่ยนแปลงสถานะ โดยจะส่งสัญญาณการเปลี่ยนแปลงสถานะก็ต่อเมื่อ หน้าเว็บไม่มีโฟกัสอินพุตอีกต่อไป (กล่าวคือ หน้าเว็บไม่ได้เพิ่งเปลี่ยน โฟกัสจากองค์ประกอบหนึ่งไปยังอีกองค์ประกอบหนึ่ง)

สถานะก่อนหน้าที่เป็นไปได้
ใช้งานอยู่

สถานะปัจจุบันที่เป็นไปได้:
passive

visibilitychange

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

สถานะก่อนหน้าที่เป็นไปได้
passive
hidden

สถานะปัจจุบันที่เป็นไปได้:
passive
hidden

freeze *

เพิ่งมีการหยุดการทำงานของหน้าเว็บ ระบบจะไม่เริ่มงานที่ หยุดชั่วคราวได้ในคิวของงานของหน้าเว็บ

สถานะก่อนหน้าที่เป็นไปได้:
ซ่อน

สถานะปัจจุบันที่เป็นไปได้:
frozen

resume *

เบราว์เซอร์ได้กลับมาทำงานในหน้าเว็บที่หยุดทำงานแล้ว

สถานะก่อนหน้าที่เป็นไปได้:
หยุดชั่วคราว

สถานะปัจจุบันที่เป็นไปได้
ใช้งานอยู่ (หากตามด้วยเหตุการณ์ pageshow)
ไม่ได้ใช้งาน (หากตามด้วยเหตุการณ์ pageshow)
ซ่อน

pageshow

กำลังข้ามไปยังรายการประวัติเซสชัน

ซึ่งอาจเป็นการโหลดหน้าเว็บใหม่หรือหน้าเว็บที่นำมาจาก Back-Forward Cache หากนำหน้ามาจาก Back-Forward Cache พร็อพเพอร์ตี้ persisted ของเหตุการณ์จะเป็น true ไม่เช่นนั้นจะเป็น false

สถานะก่อนหน้าที่เป็นไปได้:
frozen (resume เหตุการณ์จะทริกเกอร์ด้วย)

สถานะปัจจุบันที่เป็นไปได้:
active
passive
hidden

pagehide

กำลังข้ามรายการประวัติเซสชัน

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

สถานะก่อนหน้าที่เป็นไปได้:
ซ่อน

สถานะปัจจุบันที่เป็นไปได้
หยุดชั่วคราว (event.persisted เป็นจริง freeze เหตุการณ์จะเกิดขึ้นตามมา)
สิ้นสุด (event.persisted เป็นเท็จ unload เหตุการณ์จะเกิดขึ้นตามมา)

beforeunload

ระบบกำลังจะยกเลิกการโหลดหน้าต่าง เอกสาร และทรัพยากรของเอกสาร เอกสารจะยังคงปรากฏและคุณยังยกเลิกกิจกรรมได้ในตอนนี้

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

สถานะก่อนหน้าที่เป็นไปได้:
ซ่อน

สถานะปัจจุบันที่เป็นไปได้:
สิ้นสุด

unload

กำลังยกเลิกการโหลดหน้าเว็บ

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

สถานะก่อนหน้าที่เป็นไปได้:
ซ่อน

สถานะปัจจุบันที่เป็นไปได้:
สิ้นสุด

* ระบุเหตุการณ์ใหม่ที่กำหนดโดย Page Lifecycle API

ฟีเจอร์ใหม่ที่เพิ่มเข้ามาใน Chrome 68

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

ใน Chrome 68 ตอนนี้นักพัฒนาซอฟต์แวร์สามารถสังเกตได้เมื่อแท็บที่ซ่อนอยู่ถูกระงับและ เลิกการระงับโดยการฟังเหตุการณ์ freeze และ resume ใน document

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

ตั้งแต่ Chrome 68 เป็นต้นไป ออบเจ็กต์ document จะมีพร็อพเพอร์ตี้ wasDiscarded ใน Chrome บนเดสก์ท็อป (เรากำลังติดตามการรองรับ Android ในปัญหานี้) หากต้องการดูว่าระบบทิ้งหน้าเว็บขณะอยู่ในแท็บที่ซ่อนอยู่หรือไม่ คุณสามารถตรวจสอบค่าของพร็อพเพอร์ตี้นี้ในเวลาที่โหลดหน้าเว็บ (โปรดทราบว่า ต้องโหลดหน้าเว็บที่ถูกทิ้งซ้ำเพื่อใช้งานอีกครั้ง)

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

ดูคำแนะนำเกี่ยวกับสิ่งที่ควรทำในfreezeและresume เหตุการณ์ รวมถึงวิธีจัดการและเตรียมพร้อมสำหรับหน้าเว็บที่ถูกทิ้งได้ที่ คำแนะนำสำหรับนักพัฒนาซอฟต์แวร์สำหรับแต่ละสถานะ

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

วิธีสังเกตสถานะวงจรของหน้าเว็บในโค้ด

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

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

ส่วนสถานะหยุดชั่วคราวและสิ้นสุดจะตรวจหาได้ในเครื่องมือฟังเหตุการณ์ที่เกี่ยวข้อง (freeze และ pagehide) เท่านั้น เนื่องจากสถานะกำลังเปลี่ยนแปลง

วิธีสังเกตการเปลี่ยนแปลงสถานะ

คุณสามารถดูการเปลี่ยนแปลงสถานะ Page Lifecycle ทั้งหมดได้ด้วยโค้ดต่อไปนี้ โดยอิงตามฟังก์ชัน getState() ที่กำหนดไว้ก่อนหน้านี้

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// Options used for all event listeners.
const opts = {capture: true};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState()), opts);
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, opts);

window.addEventListener('pagehide', (event) => {
  // If the event's persisted property is `true` the page is about
  // to enter the back/forward cache, which is also in the frozen state.
  // If the event's persisted property is not `true` the page is
  // about to be unloaded.
  logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);

โค้ดนี้ทำ 3 สิ่งต่อไปนี้

สิ่งหนึ่งที่ควรทราบเกี่ยวกับโค้ดคือมีการเพิ่ม Listener เหตุการณ์ทั้งหมด ลงใน window และทั้งหมดจะส่งผ่าน {capture: true} ซึ่งอาจเป็นเพราะสาเหตุต่อไปนี้

  • เหตุการณ์วงจรหน้าเว็บบางรายการอาจมีเป้าหมายต่างกัน pagehide และ pageshow จะทริกเกอร์ใน window ส่วน visibilitychange, freeze และ resume จะทริกเกอร์ใน document และ focus กับ blur จะทริกเกอร์ในองค์ประกอบ DOM ที่เกี่ยวข้อง
  • เหตุการณ์ส่วนใหญ่เหล่านี้จะไม่ฟอง ซึ่งหมายความว่าคุณจะเพิ่ม Listener เหตุการณ์ที่ไม่จับภาพลงในองค์ประกอบบรรพบุรุษทั่วไปและสังเกตเหตุการณ์ทั้งหมด ไม่ได้
  • โดยเฟสการจับภาพจะทำงานก่อนเฟสเป้าหมายหรือเฟสบับเบิล ดังนั้นการเพิ่ม Listener ในเฟสนี้จะช่วยให้มั่นใจได้ว่า Listener จะทำงานก่อนที่โค้ดอื่นๆ จะยกเลิก Listener ได้

คำแนะนำสำหรับนักพัฒนาแอปสำหรับแต่ละรัฐ

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

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

รัฐ คำแนะนำสำหรับนักพัฒนาแอป
Active

สถานะใช้งานเป็นช่วงเวลาที่สําคัญที่สุดสําหรับผู้ใช้ และเป็นช่วงเวลาที่สําคัญที่สุดสําหรับหน้าเว็บของคุณในการ ตอบสนองต่ออินพุตของผู้ใช้

ควรลดลำดับความสำคัญของงานที่ไม่ใช่ UI ซึ่งอาจบล็อกเทรดหลัก เป็น ช่วงที่ไม่มีการใช้งานหรือ ส่งไปยัง Web Worker

Passive

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

เมื่อหน้าเว็บเปลี่ยนจากใช้งานอยู่เป็นไม่ได้ใช้งาน ก็เป็นเวลาที่เหมาะที่จะบันทึกสถานะแอปพลิเคชันที่ยังไม่ได้บันทึก

Hidden

เมื่อหน้าเว็บเปลี่ยนจากไม่ได้ใช้งานเป็นซ่อน ผู้ใช้อาจไม่โต้ตอบกับหน้าเว็บนั้นอีกจนกว่าจะโหลดซ้ำ

การเปลี่ยนไปเป็นซ่อนมักเป็นการเปลี่ยนแปลงสถานะครั้งสุดท้าย ที่นักพัฒนาแอปสังเกตได้อย่างน่าเชื่อถือ (โดยเฉพาะอย่างยิ่งในอุปกรณ์ เคลื่อนที่ เนื่องจากผู้ใช้สามารถปิดแท็บหรือแอปเบราว์เซอร์เองได้ และระบบจะไม่ทริกเกอร์เหตุการณ์ beforeunload, pagehide และ unload ในกรณีดังกล่าว)

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

นอกจากนี้ คุณควรหยุดการอัปเดต UI (เนื่องจากผู้ใช้จะไม่เห็น) และหยุดงานใดๆ ที่ผู้ใช้ไม่ต้องการให้ทำงานในเบื้องหลัง

Frozen

ในสถานะหยุดชั่วคราว งานที่หยุดชั่วคราวได้ใน คิวของงานจะถูกระงับจนกว่าจะมีการยกเลิกการหยุดชั่วคราวของหน้าเว็บ ซึ่งอาจ ไม่มีวันเกิดขึ้น (เช่น หากมีการทิ้งหน้าเว็บ)

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

โดยเฉพาะอย่างยิ่ง คุณควรดำเนินการต่อไปนี้

  • ปิดการเชื่อมต่อ IndexedDB ที่เปิดอยู่ทั้งหมด
  • ปิดการเชื่อมต่อ BroadcastChannel ที่เปิดอยู่
  • ปิดการเชื่อมต่อ WebRTC ที่ใช้งานอยู่
  • หยุดการสำรวจเครือข่ายหรือปิดการเชื่อมต่อ Web Socket ที่เปิดอยู่
  • ยกเลิก Web Locks ที่ระงับอยู่

นอกจากนี้ คุณควรบันทึกสถานะมุมมองแบบไดนามิก (เช่น ตำแหน่งการเลื่อน ในมุมมองรายการที่เลื่อนได้ไม่รู้จบ) ไว้ใน sessionStorage (หรือ IndexedDB ผ่าน commit()) ที่คุณต้องการกู้คืนหากระบบทิ้งหน้าเว็บ และโหลดซ้ำในภายหลัง

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

Terminated

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

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

นอกจากนี้ (ดังที่กล่าวไว้ในคำแนะนำสำหรับสถานะซ่อน) นักพัฒนาแอปควรทราบว่าในหลายกรณี (โดยเฉพาะบนอุปกรณ์เคลื่อนที่) ระบบไม่สามารถตรวจหาการเปลี่ยนไปใช้สถานะสิ้นสุดได้อย่างน่าเชื่อถือ ดังนั้นนักพัฒนาแอปที่ต้องพึ่งพาเหตุการณ์การสิ้นสุด (เช่น beforeunload, pagehide และ unload) จึงอาจสูญเสียข้อมูล

Discarded

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

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

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

API วงจรเดิมที่ควรหลีกเลี่ยง

ควรหลีกเลี่ยงเหตุการณ์ต่อไปนี้ทุกครั้งที่ทำได้

เหตุการณ์การยกเลิกการโหลด

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

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

นอกจากนี้ การมีตัวแฮนเดิลเหตุการณ์ unload ที่ลงทะเบียน (ผ่าน onunload หรือ addEventListener()) เพียงอย่างเดียวอาจทำให้เบราว์เซอร์ไม่สามารถจัดเก็บหน้าเว็บไว้ใน Back-Forward Cache เพื่อให้โหลดหน้าเว็บก่อนหน้าและถัดไปได้เร็วขึ้น

ในเบราว์เซอร์ที่ทันสมัยทั้งหมด เราขอแนะนําให้ใช้เหตุการณ์ pagehide เสมอเพื่อตรวจหาการยกเลิกการโหลดหน้าเว็บที่อาจเกิดขึ้น (หรือที่เรียกว่าสถานะสิ้นสุด) แทนเหตุการณ์ unload หากคุณต้องรองรับ Internet Explorer เวอร์ชัน 10 และต่ำกว่า คุณควรตรวจหาเหตุการณ์ pagehide และใช้ unload เฉพาะในกรณีที่เบราว์เซอร์ไม่รองรับ pagehide

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

window.addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
});

เหตุการณ์ beforeunload

เหตุการณ์ beforeunload มีปัญหาคล้ายกับเหตุการณ์ unload กล่าวคือ ในอดีต การมีเหตุการณ์ beforeunload อาจทำให้หน้าเว็บไม่มีสิทธิ์ใช้ Back-Forward Cache เบราว์เซอร์ที่ทันสมัย ไม่มีข้อจำกัดนี้ แม้ว่าเบราว์เซอร์บางตัวจะไม่ทริกเกอร์เหตุการณ์ beforeunload เมื่อพยายามใส่หน้าเว็บลงในแคชย้อนกลับ/ไปข้างหน้าเพื่อเป็นการป้องกัน ซึ่งหมายความว่าเหตุการณ์นี้ไม่น่าเชื่อถือในฐานะสัญญาณสิ้นสุดเซสชัน นอกจากนี้ เบราว์เซอร์บางตัว (รวมถึง Chrome) กำหนดให้ผู้ใช้ต้องโต้ตอบในหน้าเว็บก่อนจึงจะอนุญาตให้ทริกเกอร์เหตุการณ์ beforeunload ได้ ซึ่งส่งผลต่อความน่าเชื่อถือของเหตุการณ์ดังกล่าว

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

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

กล่าวคือ อย่าทำเช่นนี้ (เนื่องจากจะเพิ่มbeforeunload Listener โดยไม่มีเงื่อนไข)

addEventListener('beforeunload', (event) => {
  // A function that returns `true` if the page has unsaved changes.
  if (pageHasUnsavedChanges()) {
    event.preventDefault();

    // Legacy support for older browsers.
    event.returnValue = true;
  }
});

ให้ทำดังนี้แทน (เนื่องจากจะเพิ่ม Listener beforeunload เฉพาะเมื่อจำเป็น และนำออกเมื่อไม่จำเป็น)

const beforeUnloadListener = (event) => {
  event.preventDefault();

  // Legacy support for older browsers.
  event.returnValue = true;
};

// A function that adds a `beforeunload` listener if there are unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

// A function that removes the `beforeunload` listener when the page's unsaved
// changes are resolved.
onAllChangesSaved(() => {
  removeEventListener('beforeunload', beforeUnloadListener);
});

คำถามที่พบบ่อย

ทำไมจึงไม่มีสถานะ "กำลังโหลด"

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

หน้าเว็บของฉันทำงานสำคัญเมื่อซ่อนอยู่ ฉันจะหยุดไม่ให้ระบบหยุดชั่วคราวหรือทิ้งหน้าเว็บได้อย่างไร

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

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

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

  • การเล่นเสียง
  • การใช้ WebRTC
  • การอัปเดตชื่อตารางหรือ favicon
  • การแสดงการแจ้งเตือน
  • การส่งข้อความ Push

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

แคชย้อนหลังคืออะไร

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

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

การหยุดทำงานนี้มีฟังก์ชันการทำงานเหมือนกับ การหยุดทำงานของเบราว์เซอร์เพื่อประหยัด CPU/แบตเตอรี่ทุกประการ ด้วยเหตุนี้ จึงถือเป็นส่วนหนึ่งของสถานะวงจรหยุดทำงาน

หากฉันเรียกใช้ API แบบไม่พร้อมกันในสถานะหยุดทำงานหรือสิ้นสุดไม่ได้ ฉันจะบันทึกข้อมูลลงใน IndexedDB ได้อย่างไร

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

แม้ว่า API ของ IndexedDB ส่วนใหญ่จะอิงตามการเรียกกลับ แต่เมธอด commit() ในอินเทอร์เฟซ IDBTransaction จะช่วยให้เริ่มกระบวนการคอมมิตใน ธุรกรรมที่ใช้งานอยู่ได้โดยไม่ต้องรอให้ระบบส่งเหตุการณ์จากคำขอที่รอดำเนินการ ซึ่งเป็นวิธีที่เชื่อถือได้ในการบันทึกข้อมูลลงในฐานข้อมูล IndexedDB ในเครื่องมือฟังเหตุการณ์ freeze หรือ visibilitychange เนื่องจากระบบจะเรียกใช้การคอมมิตทันทีแทนที่จะจัดคิวไว้ในงานแยกต่างหาก

โดยทั่วไปจะต้องโหลดหน้าเว็บซ้ำเพื่อยืนยันว่าระบบได้บันทึกข้อมูลแล้ว

การทดสอบแอปในสถานะหยุดทำงานและสถานะทิ้ง

หากต้องการทดสอบลักษณะการทำงานของแอปในสถานะหยุดทำงานและสถานะทิ้ง คุณสามารถไปที่ chrome://discards เพื่อหยุดทำงานหรือทิ้งแท็บที่เปิดอยู่

UI ของการทิ้งแท็บใน Chrome
UI การทิ้งแท็บของ Chrome

ซึ่งจะช่วยให้คุณมั่นใจได้ว่าหน้าเว็บจะจัดการเหตุการณ์ freeze และ resume รวมถึงแฟล็ก document.wasDiscarded อย่างถูกต้องเมื่อโหลดหน้าเว็บซ้ำหลังจาก ทิ้ง

สรุป

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

ยิ่งนักพัฒนาแอปเริ่มใช้ Page Lifecycle API ใหม่มากเท่าใด เบราว์เซอร์ก็จะยิ่งปลอดภัยมากขึ้นเท่านั้น ในการระงับและทิ้งหน้าเว็บที่ไม่ได้ใช้งาน ซึ่งหมายความว่าเบราว์เซอร์จะใช้หน่วยความจำ, CPU, แบตเตอรี่ และทรัพยากรเครือข่ายน้อยลง ซึ่งเป็นประโยชน์ต่อผู้ใช้