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

การสนับสนุนเบราว์เซอร์

  • 68
  • 79
  • x
  • x

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

ที่มา

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

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

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

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

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

Page Lifecycle API จะพยายามแก้ไขปัญหานี้ดังนี้

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

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

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

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

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

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

การนำเสนอภาพขั้นตอนการดำเนินการของรัฐและเหตุการณ์ต่างๆ ซึ่งอธิบายตลอดทั้งเอกสารนี้
สถานะและโฟลว์เหตุการณ์ของ Page Lifecycle API

รัฐ

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

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

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

สถานะก่อนหน้าที่เป็นไปได้มีดังนี้
พาสซีฟ (ผ่านเหตุการณ์ focus)
ค้าง (ผ่านเหตุการณ์ resume แล้วตามด้วยเหตุการณ์ pageshow)

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

เชิงรับ

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

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

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

ซ่อนอยู่

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

สถานะก่อนหน้าที่เป็นไปได้:
พาสซีฟ (ผ่านเหตุการณ์ visibilitychange)
ค้าง (ผ่านเหตุการณ์ resume แล้วตามด้วยเหตุการณ์ pageshow)

สถานะถัดไปที่เป็นไปได้:
พาสซีฟ (ผ่านเหตุการณ์ visibilitychange)
หยุดการทำงาน (ผ่านเหตุการณ์ freeze)
ยกเลิก (ไม่มีเหตุการณ์ที่เริ่มทำงาน)
หยุดเหตุการณ์ (ไม่มีเหตุการณ์เริ่มทำงาน)

หยุดการทำงาน

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

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

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

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

ยกเลิกแล้ว

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

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

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

ทิ้งแล้ว

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

ในกรณีที่ยกเลิกแล้ว ผู้ใช้มักเห็นแท็บนั้น (รวมถึงชื่อแท็บและไอคอน Fav) แม้ว่าหน้าจะหายไปก็ตาม

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

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

กิจกรรม

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

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

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

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

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

สถานะปัจจุบันที่เป็นไปได้:
ใช้งานอยู่

blur

องค์ประกอบ DOM หายไปโฟกัส

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

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

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

visibilitychange

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

สถานะก่อนหน้าที่เป็นไปได้:
ไม่มีส่วนร่วม
ซ่อนอยู่

สถานะปัจจุบันที่เป็นไปได้:
ไม่อัปเดต
ซ่อนอยู่

freeze *

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

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

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

resume *

เบราว์เซอร์ได้กลับสู่หน้าที่ค้างอีกครั้ง

สถานะก่อนหน้าที่เป็นไปได้:
ตรึง

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

pageshow

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

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

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

สถานะปัจจุบันที่เป็นไปได้:
ใช้งานอยู่
พาสซีฟ
ซ่อน

pagehide

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

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

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

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

beforeunload

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

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

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

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

unload

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

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

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

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

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

เพิ่มฟีเจอร์ใหม่ใน Chrome 68 แล้ว

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

ตอนนี้นักพัฒนาแอปใน 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';
};

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

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

การสร้างบนฟังก์ชัน 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 สิ่งต่อไปนี้

  • ตั้งสถานะเริ่มต้นโดยใช้ฟังก์ชัน getState()
  • กำหนดฟังก์ชันที่ยอมรับสถานะถัดไป และหากมีการเปลี่ยนแปลง ระบบจะบันทึกการเปลี่ยนแปลงสถานะไปยังคอนโซล
  • เพิ่ม การบันทึก Listener เหตุการณ์สำหรับเหตุการณ์ในวงจรทั้งหมดที่จำเป็น ซึ่งส่งผลให้เป็นการเรียกใช้ logStateChange() ผ่านในสถานะถัดไป

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

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

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

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

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

รัฐ คำแนะนำสำหรับนักพัฒนาซอฟต์แวร์
Active

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

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

Passive

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

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

Hidden

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

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

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

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

Frozen

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

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

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

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

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

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

Terminated

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

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

นอกจากนี้ (ดังที่ระบุไว้ในคำแนะนำสำหรับสถานะซ่อน) สิ่งสำคัญอย่างยิ่งสำหรับนักพัฒนาซอฟต์แวร์ต้องตระหนักว่าการเปลี่ยนเป็นสถานะสิ้นสุดนั้นไม่สามารถตรวจพบได้อย่างน่าเชื่อถือในหลายกรณี (โดยเฉพาะบนอุปกรณ์เคลื่อนที่) ดังนั้นนักพัฒนาแอปที่อาศัยเหตุการณ์การสิ้นสุด (เช่น 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 เมื่อพยายามใส่หน้าใน Back-Forward Cache ซึ่งหมายความว่าเหตุการณ์ไม่ถือว่าเป็นสัญญาณสิ้นสุดเซสชัน นอกจากนี้ เบราว์เซอร์บางประเภท (รวมถึง Chrome) ยังต้องมีการโต้ตอบจากผู้ใช้ในหน้าเว็บก่อนที่จะอนุญาตให้เหตุการณ์ beforeunload เริ่มทํางาน ซึ่งจะส่งผลต่อความเสถียรยิ่งขึ้น

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

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

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

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

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

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

const beforeUnloadListener = (event) => {
  event.preventDefault();
  
  // Legacy support for older browsers.
  return (event.returnValue = true);
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

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

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

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

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

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

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

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

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

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

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

Back-Forward Cache คืออะไร

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

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

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

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

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

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

แต่สำหรับโค้ดที่ต้องใช้งานได้ในปัจจุบัน นักพัฒนาซอฟต์แวร์จะมี 2 ตัวเลือก ได้แก่

  • ใช้พื้นที่เก็บข้อมูลเซสชัน: พื้นที่เก็บข้อมูลเซสชันเป็นแบบซิงโครนัสและยังคงอยู่ในการทิ้งหน้าเว็บ
  • ใช้ IndexedDB จาก Service Worker: โปรแกรมทำงานของบริการสามารถเก็บข้อมูลใน IndexedDB หลังจากที่หน้าถูกยกเลิกหรือยกเลิกแล้ว ใน Listener เหตุการณ์ freeze หรือ pagehide คุณจะส่งข้อมูลไปยัง Service Worker ผ่าน postMessage() ได้ และ Service Worker ก็สามารถบันทึกข้อมูลได้

การทดสอบแอปอยู่ในสถานะค้างและถูกยกเลิก

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

Chrome ยกเลิก UI
Chrome ยกเลิก UI

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

สรุป

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

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