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

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

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

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

ข้อมูลเบื้องต้น

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

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

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

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

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

Page Lifecycle API จะพยายามแก้ปัญหานี้ด้วยวิธีต่อไปนี้

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

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

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

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

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

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

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

รัฐ

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

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

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

สถานะก่อนหน้าที่เป็นไปได้:
passive (ผ่านเหตุการณ์ focus)
frozen (ผ่านเหตุการณ์ resume แล้วตามด้วยเหตุการณ์ pageshow)

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

เชิงรับ

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

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

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

ซ่อน

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

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

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

ค้าง

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

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

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

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

สิ้นสุดแล้ว

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

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

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

ทิ้งแล้ว

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

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

สถานะก่อนหน้าที่เป็นไปได้:
hidden (no events fired)
frozen (no events fired)

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

กิจกรรม

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

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

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

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

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

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

blur

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

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

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

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

visibilitychange

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

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

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

freeze *

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

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

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

resume *

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

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

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

pageshow

กําลังไปยังรายการประวัติเซสชัน

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

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

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

pagehide

รายการประวัติเซสชันที่กําลังสํารวจ

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

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

สถานะปัจจุบันที่เป็นไปได้:
frozen (event.persisted is true, freeze event follows)
terminated (event.persisted is false, unload event follows)

beforeunload

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

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

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

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

unload

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

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

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

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

* บ่งบอกถึงเหตุการณ์ใหม่ที่ 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()
  • กำหนดฟังก์ชันที่ยอมรับสถานะถัดไป และหากมีการเปลี่ยนแปลง ระบบจะบันทึกการเปลี่ยนแปลงสถานะลงในคอนโซล
  • เพิ่ม capturing Listener เหตุการณ์สําหรับเหตุการณ์วงจรชีวิตของจําเป็นทั้งหมด ซึ่งจะเรียก logStateChange() โดยส่งสถานะถัดไป

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

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

คําแนะนําของนักพัฒนาแอปสําหรับแต่ละสถานะ

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

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

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

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

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

Passive

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

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

Hidden

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

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

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

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

Frozen

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

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

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

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

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

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

Terminated

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

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

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

Discarded

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

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

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

API วงจรของลูกค้าเดิมที่ควรหลีกเลี่ยง

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

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

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

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

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

ในเบราว์เซอร์สมัยใหม่ทั้งหมด เราขอแนะนำให้ใช้เหตุการณ์ 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 เราจึงขอแนะนำให้คุณเพิ่มผู้ฟัง 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.
    return (event.returnValue = true);
  }
});

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

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
  • การอัปเดตชื่อตารางหรือ Favicon
  • การแสดงการแจ้งเตือน
  • การส่งข้อความ Push

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

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

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

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

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

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

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

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

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

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

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

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

UI การทิ้งของ Chrome
UI ของ Chrome Discards

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

สรุป

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

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