ทำให้การเลื่อนด้วยการแตะทำงานเร็วโดยค่าเริ่มต้น

Dave Tapuska
Dave Tapuska

เราทราบดีว่าการตอบสนองต่อการเลื่อนเป็นสิ่งสำคัญต่อการมีส่วนร่วมของผู้ใช้กับเว็บไซต์บนอุปกรณ์เคลื่อนที่ แต่โปรแกรมรับฟังเหตุการณ์การสัมผัสมักทำให้เกิดปัญหาด้านประสิทธิภาพการเลื่อนอย่างร้ายแรง Chrome แก้ไขปัญหานี้ด้วยการอนุญาตให้ใช้ passive (ส่งตัวเลือก {passive: true} ไปยัง addEventListener()) กับ pointer events API ฟีเจอร์เหล่านี้เป็นฟีเจอร์ที่ยอดเยี่ยมในการขับเคลื่อนเนื้อหาใหม่ไปยังรูปแบบที่ไม่บล็อกการเลื่อน แต่บางครั้งนักพัฒนาแอปอาจเข้าใจและนำไปใช้ยาก

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

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

เบื้องหลัง: เหตุการณ์ที่ยกเลิกได้ทําให้หน้าเว็บช้าลง

หากคุณเรียกใช้ preventDefault() ในเหตุการณ์ touchstart หรือ touchmove รายการแรก จะเป็นการป้องกันการเลื่อน ปัญหาคือส่วนใหญ่ Listeners จะไม่เรียก preventDefault() แต่เบราว์เซอร์ต้องรอให้เหตุการณ์เสร็จสิ้นจึงจะแน่ใจได้ "Listener เหตุการณ์แบบแพสซีฟ" ที่นักพัฒนาแอปกำหนดจะแก้ปัญหานี้ได้ เมื่อคุณเพิ่มเหตุการณ์การแตะที่มีออบเจ็กต์ {passive: true} เป็นพารามิเตอร์ที่ 3 ในตัวแฮนเดิลเหตุการณ์ แสดงว่าคุณกำลังบอกเบราว์เซอร์ว่าตัวฟัง touchstart จะไม่เรียก preventDefault() และเบราว์เซอร์สามารถเลื่อนได้อย่างปลอดภัยโดยไม่ต้องบล็อกตัวฟัง เช่น

window.addEventListener("touchstart", func, {passive: true} );

The Intervention

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

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

ด้วยเหตุนี้ เราจึงกำหนดการแทรกแซงของเราว่า: หากเป้าหมายของ Listener ของ touchstart หรือ touchmove คือ window, document หรือ body เราจะตั้งค่าเริ่มต้น passive เป็น true ซึ่งหมายความว่าโค้ดอย่างเช่น

window.addEventListener("touchstart", func);

จะกลายเป็น

window.addEventListener("touchstart", func, {passive: true} );

ตอนนี้การเรียก preventDefault() ภายใน Listener จะถูกละเว้น

กราฟด้านล่างแสดงเวลาที่ใช้ในการเลื่อน 1% อันดับสูงสุดนับจากเวลาที่ผู้ใช้แตะหน้าจอเพื่อเลื่อนไปจนถึงเวลาที่หน้าจออัปเดต ข้อมูลนี้ใช้กับทุกเว็บไซต์ใน Chrome สำหรับ Android ก่อนที่จะเปิดใช้การแทรกแซง การเลื่อน 1% ใช้เวลาเพียง 400 มิลลิวินาที ตอนนี้ลดลงเหลือเพียง 250 มิลลิวินาทีใน Chrome 56 เบต้า ซึ่งลดลงประมาณ 38% ในอนาคตเราหวังว่าจะทำให้ตัวเลือก passive true เป็นค่าเริ่มต้นสำหรับทั้งหมดของ touchstart และ touchmove Listeners เพื่อลดเวลานี้ให้เหลือต่ำกว่า 50 มิลลิวินาที

กราฟเวลาในการเลื่อน 1% สูงสุด

การแตกหักและคำแนะนำ

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

ใน Chrome 56 ขึ้นไป DevTools จะบันทึกคำเตือนเมื่อคุณเรียกใช้ preventDefault() ในเหตุการณ์ที่มีการแทรกแซง

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

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

เราพบว่าหน้าเว็บส่วนใหญ่ที่ได้รับผลกระทบแก้ไขได้ง่ายโดยใช้พร็อพเพอร์ตี้ CSS touch-action ทุกครั้งที่เป็นไปได้ หากต้องการป้องกันไม่ให้เบราว์เซอร์เลื่อนและซูมภายในองค์ประกอบทั้งหมด ให้ใช้ touch-action: none กับองค์ประกอบนั้น หากคุณมีภาพสไลด์แนวนอน ให้ลองใช้ touch-action: pan-y pinch-zoom กับภาพสไลด์ดังกล่าวเพื่อให้ผู้ใช้ยังเลื่อนในแนวตั้งและซูมได้ตามปกติ การใช้แอตทริบิวต์ touch-action อย่างถูกต้องเป็นสิ่งจําเป็นอยู่แล้วในเบราว์เซอร์ เช่น Edge บนเดสก์ท็อป ที่รองรับเหตุการณ์ Pointer แต่ไม่ใช่เหตุการณ์ Touch สำหรับ Safari บนอุปกรณ์เคลื่อนที่และเบราว์เซอร์บนอุปกรณ์เคลื่อนที่รุ่นเก่าที่ไม่รองรับการแตะเพื่อดำเนินการ โปรแกรมฟังการแตะของคุณต้องเรียกใช้ preventDefault ต่อไปแม้ว่า Chrome จะละเว้นก็ตาม

ในกรณีที่ซับซ้อนมากขึ้น คุณอาจต้องใช้วิธีใดวิธีหนึ่งต่อไปนี้ด้วย

  • หาก touchstart listener เรียก preventDefault() ให้ตรวจสอบว่ามีการเรียก preventDefault() จาก Listener ของ touchend ที่เชื่อมโยงด้วยเพื่อระงับการสร้างเหตุการณ์การคลิกและลักษณะการแตะเริ่มต้นอื่นๆ ต่อไป
  • สุดท้าย (และไม่ควรทำ) ให้ส่ง {passive: false} ไปยัง addEventListener() เพื่อลบล้างลักษณะการทำงานเริ่มต้น โปรดทราบว่าคุณจะต้องตรวจหาฟีเจอร์ว่า User Agent รองรับ EventListenerOptions หรือไม่

บทสรุป

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

แม้ว่าจะยังคงจําเป็นต้องทำเช่นนั้นสําหรับ Safari บนอุปกรณ์เคลื่อนที่ แต่เว็บไซต์ไม่ควรพึ่งพาการเรียก preventDefault() ภายใน touchstart และ touchmove Listeners เนื่องจาก Chrome ไม่รับประกันว่าจะดำเนินการดังกล่าวอีกต่อไป นักพัฒนาซอฟต์แวร์ควรใช้พร็อพเพอร์ตี้ touch-action CSS ในองค์ประกอบที่ควรปิดใช้การเลื่อนและการซูมเพื่อแจ้งให้เบราว์เซอร์ทราบก่อนที่จะเกิดเหตุการณ์การสัมผัส หากต้องการระงับลักษณะการทํางานเริ่มต้นของการแตะ (เช่น การสร้างเหตุการณ์การคลิก) ให้เรียกใช้ preventDefault() ภายใน touchend Listener