เจาะลึกเว็บเบราว์เซอร์สมัยใหม่ (ตอนที่ 4)

มาริโกะ โคซากะ

กำลังป้อนอินพุตไปยังตัวองค์ประกอบ

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

ป้อนเหตุการณ์จากมุมมองของเบราว์เซอร์

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

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

เหตุการณ์อินพุต
ภาพที่ 1: เหตุการณ์อินพุตกำหนดเส้นทางผ่านกระบวนการของเบราว์เซอร์ไปยังกระบวนการของโหมดแสดงภาพ

คอมโพสิตได้รับเหตุการณ์อินพุต

ภาพที่ 2: วิวพอร์ตวางอยู่เหนือเลเยอร์ของหน้าเว็บ

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

ทำความเข้าใจภูมิภาคแบบเลื่อนได้ที่ไม่เร็ว

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

ภูมิภาคที่เลื่อนได้ไม่เร็วแบบจำกัด
ภาพที่ 3: แผนภาพอินพุตที่อธิบายส่วนภูมิภาคที่เลื่อนได้แบบรวดเร็ว

ระมัดระวังเมื่อคุณเขียนเครื่องจัดการเหตุการณ์

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

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

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

ภูมิภาคที่เลื่อนได้ไม่เร็วทั้งหน้า
รูปที่ 4: แผนภาพอินพุตที่อธิบายข้อมูลส่วนที่ไม่สามารถเลื่อนดูได้อย่างรวดเร็วซึ่งครอบคลุมหน้าเว็บทั้งหน้า

คุณสามารถส่งตัวเลือก passive: true ในตัวฟังเหตุการณ์เพื่อลดปัญหานี้ คำแนะนำนี้สำหรับเบราว์เซอร์ที่คุณยังคงต้องการฟังเหตุการณ์ในเทรดหลัก แต่เครื่องมือทำองค์ประกอบก็สามารถใส่เฟรมใหม่ได้เลย

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

ตรวจสอบว่ากิจกรรมยกเลิกได้หรือไม่

การเลื่อนหน้า
ภาพที่ 5: หน้าเว็บที่มีบางส่วนของหน้าอยู่กับการเลื่อนในแนวนอน

ลองนึกภาพว่าคุณมีช่องในหน้าที่ต้องการจำกัดทิศทางการเลื่อนให้เป็นการเลื่อนในแนวนอนเท่านั้น

การใช้ตัวเลือก passive: true ในเหตุการณ์ตัวชี้จะทำให้การเลื่อนหน้าเว็บเป็นไปอย่างราบรื่น แต่การเลื่อนแนวตั้งอาจเริ่มจากเวลาที่คุณต้องการpreventDefaultเพื่อจำกัดทิศทางการเลื่อน คุณตรวจสอบปัญหานี้ได้โดยใช้เมธอด event.cancelable

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

หรือคุณอาจใช้กฎ CSS เช่น touch-action เพื่อกำจัดเครื่องจัดการเหตุการณ์โดยสมบูรณ์

#area {
  touch-action: pan-x;
}

หาเป้าหมายของเหตุการณ์

การทดสอบ Hit
รูปที่ 6: เทรดหลักที่กำลังดูระเบียนสี ซึ่งถามสิ่งที่วาดบนจุด x.y

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

การลดการส่งเหตุการณ์ไปยังเทรดหลัก

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

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

กิจกรรมที่ไม่กรอง
ภาพที่ 7: เหตุการณ์ที่เกิดขึ้นในไทม์ไลน์ของเฟรมทำให้หน้ากระตุก

Chrome จะจัดกลุ่มเหตุการณ์ต่อเนื่อง (เช่น wheel, mousewheel, mousemove, pointermove, touchmove ) และหน่วงเวลาการส่งโฆษณาจนถึง requestAnimationFrame ถัดไป เพื่อลดการเรียกใช้เทรดหลักมากเกินไป

กิจกรรมที่รวมเป็นชุด
รูปที่ 8: ไทม์ไลน์เหมือนกับแต่ก่อน แต่มีการรวมกิจกรรมและหน่วงเวลา

ระบบจะส่งเหตุการณ์แยกต่างหากทั้งหมด เช่น keydown, keyup, mouseup, mousedown, touchstart และ touchend ทันที

ใช้ getCoalescedEvents เพื่อรับเหตุการณ์ภายในเฟรม

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

getCoalescedEvents
รูปที่ 9: เส้นทางท่าทางสัมผัสแบบแตะที่ลื่นไหลทางด้านซ้าย เส้นทางจำกัดแบบซ้อนอยู่ทางด้านขวา
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

ขั้นตอนถัดไป

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

ใช้ Lighthouse

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

ดูวิธีวัดประสิทธิภาพ

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

เพิ่มนโยบายฟีเจอร์ลงในเว็บไซต์

หากคุณต้องการดำเนินการเพิ่มเติมอีกขั้น นโยบายฟีเจอร์คือฟีเจอร์ใหม่ของแพลตฟอร์มบนเว็บที่อาจเป็นแนวทางให้คุณเมื่อสร้างโปรเจ็กต์ การเปิดนโยบายฟีเจอร์รับประกันลักษณะการทำงานบางอย่างของแอปและป้องกันไม่ให้คุณทำงานผิดพลาด ตัวอย่างเช่น หากต้องการดูแลไม่ให้แอปบล็อกการแยกวิเคราะห์ ให้เรียกใช้แอปตามนโยบายสคริปต์แบบซิงโครนัส เมื่อเปิดใช้ sync-script: 'none' ระบบจะป้องกันไม่ให้ JavaScript ที่บล็อกโปรแกรมแยกวิเคราะห์ทำงาน ซึ่งจะป้องกันไม่ให้โค้ดของคุณบล็อกโปรแกรมแยกวิเคราะห์ และเบราว์เซอร์ก็ไม่ต้องกังวลเรื่องการหยุดโปรแกรมแยกวิเคราะห์ชั่วคราว

สรุป

ขอบคุณ

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

ขอขอบคุณทุกคนที่รีวิวเนื้อหาฉบับร่างช่วงแรกๆ ของซีรีส์นี้ ซึ่งรวมถึง (แต่ไม่จำกัดเพียง Alex Russell, Paul Ireland, Meggin Kearney, Eric Bidelman, Mathias Bynens, Addy Osmani, Kinuko Yasaskie, Kinuko Yasaskie

คุณชอบซีรีส์นี้ไหม หากคุณมีคำถามหรือคำแนะนำสำหรับโพสต์ในอนาคต เรายินดีรับฟังความคิดเห็นจากคุณในส่วนความคิดเห็นด้านล่างหรือ @kosamari บน Twitter