Chrome 88 (มกราคม 2021) จะควบคุมตัวจับเวลา JavaScript ที่เชื่อมโยงกันอย่างหนักสำหรับหน้าเว็บที่ซ่อนไว้ในบางเงื่อนไข วิธีนี้จะลดการใช้งาน CPU ซึ่งจะช่วยลดการใช้งานแบตเตอรี่ อาจมีบางกรณีที่การทำเช่นนี้อาจเปลี่ยนลักษณะการทำงานได้ แต่มีการใช้ตัวจับเวลาในกรณีที่ API อื่นจะมีประสิทธิภาพมากกว่าและน่าเชื่อถือมากกว่า
โอเค ศัพท์ศัพท์เฉพาะทางค่อนข้างใหญ่และกำกวมไปหน่อยนะ มาเริ่มกันเลย...
คำศัพท์
หน้าที่ซ่อน
โดยทั่วไป ซ่อนอยู่หมายความว่ามีแท็บอื่นทำงานอยู่ หรือหน้าต่างถูกย่อขนาดลง แต่เบราว์เซอร์อาจพิจารณาว่ามีการซ่อนหน้าไว้ทุกครั้งที่เนื้อหาของหน้านั้นมองไม่เห็นเลย บางเบราว์เซอร์จะทำงานได้มากกว่าเบราว์เซอร์อื่นๆ แต่คุณสามารถใช้ API การเปิดเผยหน้าเว็บได้เสมอเพื่อติดตามเมื่อเบราว์เซอร์คิดว่าระดับการเข้าถึงมีการเปลี่ยนแปลง
ตัวจับเวลา JavaScript
ตัวจับเวลา JavaScript หมายถึง setTimeout
และ setInterval
ซึ่งช่วยให้คุณกำหนดเวลาการติดต่อกลับในอนาคตได้ ตัวจับเวลามีประโยชน์และก็ไม่ได้หายไปไหน แต่บางครั้งก็ใช้ในการระบุแบบสำรวจเมื่อเหตุการณ์จะมีประสิทธิภาพและแม่นยำยิ่งขึ้น
ตัวจับเวลาแบบโซ่
หากคุณเรียกใช้ setTimeout
ในงานเดียวกันกับการเรียกกลับ setTimeout
ระบบจะ "เชน" การเรียกใช้ครั้งที่ 2 เมื่อใช้ setInterval
การทำซ้ำแต่ละครั้งเป็นส่วนหนึ่งของเชน ซึ่งอาจเข้าใจง่ายขึ้นเมื่อใช้โค้ด
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
และ:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
วิธีการทำงานของการควบคุม
การควบคุมจะเกิดขึ้นเป็นขั้นๆ ดังนี้
การควบคุมแบบเรียบง่าย
กรณีนี้เกิดขึ้นกับตัวจับเวลาที่กําหนดเวลาไว้เมื่อข้อใดข้อหนึ่งต่อไปนี้เป็นจริง
- หน้าเว็บมองเห็นได้
- หน้าเว็บมีเสียงรบกวนในช่วง 30 วินาทีที่ผ่านมา ซึ่งอาจมาจาก API สร้างเสียงก็ได้ แต่ระบบไม่นับแทร็กเสียงแบบปิดเสียง
ระบบจะไม่ควบคุมตัวจับเวลา เว้นแต่ระยะหมดเวลาที่ขอจะน้อยกว่า 4 มิลลิวินาที และจำนวนเชนคือ 5 ขึ้นไป ซึ่งในกรณีนี้ระบบจะตั้งค่าระยะหมดเวลาเป็น 4 มิลลิวินาที ไม่ใช่เรื่องใหม่เลย เบราว์เซอร์ทำงานนี้มาหลายปีแล้ว
การควบคุม
ซึ่งจะเกิดขึ้นกับตัวจับเวลาที่ตั้งเวลาไว้เมื่อไม่ได้ใช้การควบคุมเล็กน้อย และข้อใดข้อหนึ่งต่อไปนี้เป็นจริง
- จำนวนเชนน้อยกว่า 5
- หน้าเว็บถูกซ่อนไว้น้อยกว่า 5 นาที
- ใช้งาน WebRTC อยู่ กล่าวอย่างเจาะจงคือ มี
RTCPeerConnection
ที่มีRTCDataChannel
แบบ "เปิด" หรือMediaStreamTrack
แบบ "สด"
เบราว์เซอร์จะตรวจสอบตัวจับเวลาในกลุ่มนี้ 1 ครั้งต่อวินาที เนื่องจากระบบจะตรวจสอบตัวจับเวลาเหล่านี้เพียงครั้งเดียวต่อวินาที ตัวจับเวลาที่มีระยะหมดเวลาใกล้เคียงกันจะจัดกลุ่มไว้ด้วยกัน โดยรวมเวลาที่แท็บต้องใช้ในการเรียกใช้โค้ด ซึ่งก็ไม่ใช่เรื่องใหม่ด้วย เบราว์เซอร์ต่างๆ ได้ทำสิ่งนี้มาหลายปีแล้ว
การควบคุมอย่างเข้มงวด
โอเค นี่คือบิตใหม่ใน Chrome 88 การควบคุมแบบเข้มงวดจะเกิดขึ้นกับตัวจับเวลาที่มีการกําหนดเวลาไว้เมื่อไม่มีเงื่อนไขการควบคุมเล็กน้อยหรือการควบคุม และเงื่อนไขทั้งหมดต่อไปนี้เป็นจริง
- หน้าเว็บถูกซ่อนไว้นานกว่า 5 นาที
- จำนวนเชนเท่ากับ 5 ขึ้นไป
- หน้าเว็บไม่ได้ปิดเสียงเป็นเวลาอย่างน้อย 30 วินาที
- ไม่มีการใช้งาน WebRTC
ในกรณีนี้ เบราว์เซอร์จะตรวจสอบตัวจับเวลาในกลุ่มนี้ 1 ครั้งต่อนาที เช่นเดียวกับก่อนหน้านี้ ตัวจับเวลาจะจัดกลุ่มไว้ด้วยกันในการตรวจสอบแบบนาทีต่อนาที
วิธีแก้ปัญหา
ปกติแล้วมีทางเลือกอื่นที่ดีกว่าตัวจับเวลา หรือสามารถรวมตัวจับเวลากับตัวอื่นๆ เพื่อช่วยลดปัญหา CPU และอายุการใช้งานแบตเตอรี่
การจัดทำแบบสำรวจของรัฐ
นี่คือการใช้ตัวจับเวลาที่เกิดขึ้นบ่อยที่สุด (ผิดวิธี) ซึ่งเป็นที่ที่ผู้คนจะใช้เพื่อตรวจสอบหรือสำรวจอย่างต่อเนื่องเพื่อดูว่ามีการเปลี่ยนแปลงใดๆ หรือไม่ ในกรณีส่วนใหญ่จะมีpush ที่เทียบเท่ากัน ซึ่งจะบอกให้คุณทราบเกี่ยวกับการเปลี่ยนแปลงเมื่อเกิดขึ้น คุณจึงไม่ต้องคอยตรวจสอบต่อไป ลองตรวจสอบว่ามีเหตุการณ์ที่ประสบความสำเร็จแบบเดียวกันหรือไม่
ตัวอย่างมีดังต่อไปนี้
- หากต้องการทราบเมื่อมีองค์ประกอบเข้าสู่วิวพอร์ต ให้ใช้
IntersectionObserver
- หากคุณต้องการทราบเมื่อองค์ประกอบเปลี่ยนขนาด ให้ใช้
ResizeObserver
- หากต้องการทราบเมื่อ DOM เปลี่ยนแปลง ให้ใช้
MutationObserver
หรืออาจใช้โค้ดเรียกกลับสำหรับวงจรองค์ประกอบที่กำหนดเอง - ให้พิจารณาเว็บซ็อกเก็ต เหตุการณ์ที่ส่งจากเซิร์ฟเวอร์ ข้อความพุช หรือดึงสตรีมแทนการสำรวจเซิร์ฟเวอร์
- หากต้องการตอบสนองต่อการเปลี่ยนแปลงระยะในเสียง/วิดีโอ ให้ใช้เหตุการณ์ เช่น
timeupdate
และended
หรือrequestVideoFrameCallback
หากต้องการดำเนินการบางอย่างกับแต่ละเฟรม
นอกจากนี้ยังมีทริกเกอร์การแจ้งเตือนหากคุณต้องการแสดงการแจ้งเตือนในเวลาที่เฉพาะเจาะจง
แอนิเมชัน
ภาพเคลื่อนไหวเป็นสิ่งที่เป็นภาพ ดังนั้นจึงไม่ควรใช้เวลา CPU เมื่อหน้าเว็บซ่อนอยู่
requestAnimationFrame
กำหนดเวลาการทำงานของภาพเคลื่อนไหวได้ดีกว่าตัวจับเวลา JavaScript โดยจะซิงค์กับอัตราการรีเฟรชของอุปกรณ์เพื่อให้คุณได้รับโค้ดเรียกกลับเพียง 1 ครั้งต่อเฟรมที่แสดงได้ และคุณมีเวลาสูงสุดในการสร้างเฟรมดังกล่าว นอกจากนี้ requestAnimationFrame
จะรอให้หน้าเว็บปรากฏขึ้น จึงไม่ใช้ CPU ใดๆ เมื่อมีการซ่อนหน้าไว้
หากคุณประกาศภาพเคลื่อนไหวทั้งหมดไว้ล่วงหน้าได้ ให้ลองใช้ภาพเคลื่อนไหวของ CSS หรือ API ภาพเคลื่อนไหวในเว็บ เบราว์เซอร์เหล่านี้มีข้อดีเช่นเดียวกับ requestAnimationFrame
แต่เบราว์เซอร์สามารถเพิ่มประสิทธิภาพเพิ่มเติม เช่น การประสานข้อมูลอัตโนมัติ และโดยทั่วไปแล้วจะใช้งานง่ายกว่า
หากภาพเคลื่อนไหวมีอัตราเฟรมต่ำ (เช่น เคอร์เซอร์ที่กะพริบ) ตัวจับเวลายังคงเป็นตัวเลือกที่ดีที่สุดในขณะนี้ แต่คุณจะรวมกับ requestAnimationFrame
เพื่อใช้ประโยชน์จากทั้ง 2 ด้านได้ ดังนี้
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
การใช้งาน
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
การทดสอบ
การเปลี่ยนแปลงนี้จะเปิดใช้กับผู้ใช้ Chrome ทุกคนใน Chrome 88 (มกราคม 2021) แต่ตอนนี้มีให้บริการสำหรับผู้ใช้ Chrome เบต้า นักพัฒนา และ Canary 50% หากต้องการทดสอบ ให้ใช้แฟล็กบรรทัดคำสั่งนี้เมื่อเปิด Chrome เบต้า เวอร์ชันที่กำลังพัฒนา หรือ Canary
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
อาร์กิวเมนต์ grace_period_seconds/10
ทำให้เกิดการควบคุมแบบเข้มข้นในช่วงหลังจาก 10 วินาทีของหน้าเว็บที่ซ่อนอยู่ แทนที่จะเป็นแบบเต็ม 5 นาที ซึ่งทำให้เห็นผลกระทบของการควบคุมได้ง่ายขึ้น
อนาคต
เนื่องจากตัวจับเวลาเป็นการใช้ CPU มากเกินไป เราจึงจะมาทบทวนวิธีควบคุมการทำงานโดยไม่ทำให้เนื้อหาเว็บเสียหาย และ API ที่เราสามารถเพิ่ม/เปลี่ยนแปลงเพื่อให้ตรงตามกรณีการใช้งานได้ โดยส่วนตัวแล้ว ผมไม่อยากใช้ animationInterval
เพื่อใช้โค้ดเรียกกลับของภาพเคลื่อนไหวความถี่ต่ำที่มีประสิทธิภาพ หากมีข้อสงสัย โปรดติดต่อเราทาง Twitter
รูปภาพส่วนหัวโดย Heather Zabriskie ใน Unsplash