ชี้ไปข้างหน้า

Sérgio Gomes

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

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

เรามีเหตุการณ์การแตะมาระยะหนึ่งแล้วเพื่อช่วยแก้ปัญหานี้ แต่เหตุการณ์ดังกล่าวเป็น API ที่แยกต่างหากทั้งหมดสําหรับการแตะโดยเฉพาะ ซึ่งทําให้คุณต้องเขียนโค้ดรูปแบบเหตุการณ์แยกกัน 2 รูปแบบหากต้องการรองรับทั้งเมาส์และการแตะ Chrome 55 มาพร้อมกับมาตรฐานใหม่ซึ่งรวมรูปแบบทั้ง 2 รูปแบบเข้าด้วยกัน ได้แก่ เหตุการณ์เคอร์เซอร์

รูปแบบเหตุการณ์เดียว

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

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

ต่อไปนี้คือรายการเหตุการณ์ทั้งหมดที่ใช้ได้ ซึ่งน่าจะดูคุ้นเคยหากคุณคุ้นเคยกับเหตุการณ์เมาส์

pointerover เคอร์เซอร์เข้าไปในกรอบพื้นที่ขององค์ประกอบ การดำเนินการนี้จะเกิดขึ้นทันทีสำหรับอุปกรณ์ที่รองรับการโฮเวอร์ หรือก่อนเหตุการณ์ pointerdown สำหรับอุปกรณ์ที่ไม่รองรับ
pointerenter คล้ายกับ pointerover แต่ไม่ทําให้ค่าบับเบิลและจัดการกับรายการสืบทอดในลักษณะที่ต่างกัน รายละเอียดเกี่ยวกับข้อกำหนด
pointerdown เคอร์เซอร์เข้าสู่สถานะปุ่มที่ใช้งานอยู่ โดยมีการปุ่มกดหรือมีการเชื่อมต่อ ขึ้นอยู่กับความหมายของอุปกรณ์อินพุต
pointermove เคอร์เซอร์เปลี่ยนตำแหน่งแล้ว
pointerup เคอร์เซอร์ออกจากสถานะปุ่มที่ใช้งานอยู่
pointercancel มีบางอย่างเกิดขึ้นซึ่งหมายความว่าเคอร์เซอร์จะไม่ส่งเหตุการณ์อีก ซึ่งหมายความว่าคุณควรยกเลิกการดำเนินการที่กำลังดำเนินอยู่และกลับไปที่สถานะอินพุตที่เป็นกลาง
pointerout เคอร์เซอร์ออกจากกล่องขอบเขตขององค์ประกอบหรือหน้าจอ และหลังจาก pointerup หากอุปกรณ์ไม่รองรับการโฮเวอร์
pointerleave คล้ายกับ pointerout แต่ไม่ทําให้ค่าบับเบิลและจัดการกับรายการสืบทอดในลักษณะที่ต่างกัน รายละเอียดเกี่ยวกับข้อกำหนด
gotpointercapture องค์ประกอบได้รับการจับเคอร์เซอร์
lostpointercapture ปล่อยเคอร์เซอร์ที่กำลังจับภาพแล้ว

อินพุตประเภทต่างๆ

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

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

การดำเนินการเริ่มต้น

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

เมื่อใช้เหตุการณ์เคอร์เซอร์ เมื่อใดก็ตามที่ทริกเกอร์การดําเนินการเริ่มต้น เช่น การเลื่อนหรือซูม คุณจะได้รับเหตุการณ์ pointercancel เพื่อแจ้งให้ทราบว่าเบราว์เซอร์ได้ควบคุมเคอร์เซอร์แล้ว เช่น

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

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

คุณหยุดไม่ให้เบราว์เซอร์ควบคุมได้ด้วยพร็อพเพอร์ตี้ CSS ของ touch-action การตั้งค่าเป็น none ในองค์ประกอบจะปิดใช้การดำเนินการทั้งหมดที่เบราว์เซอร์กำหนดไว้ซึ่งเริ่มต้นในองค์ประกอบนั้น แต่ก็มีค่าอื่นๆ อีกหลายค่าสำหรับการควบคุมที่ละเอียดยิ่งขึ้น เช่น pan-x สำหรับอนุญาตให้เบราว์เซอร์ตอบสนองต่อการเคลื่อนไหวในแนวแกน x แต่ไม่ใช่แนวแกน y Chrome 55รองรับค่าต่อไปนี้

auto ค่าเริ่มต้น เบราว์เซอร์จะดําเนินการเริ่มต้นได้
none เบราว์เซอร์ไม่ได้รับอนุญาตให้ดําเนินการเริ่มต้นใดๆ
pan-x เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการเริ่มต้นในการเลื่อนในแนวนอนเท่านั้น
pan-y เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการเริ่มต้นด้วยการเลื่อนในแนวตั้งเท่านั้น
pan-left เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการเริ่มต้นในการเลื่อนในแนวนอนเท่านั้น และเพื่อเลื่อนหน้าไปทางซ้ายเท่านั้น
pan-right เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการเริ่มต้นในการเลื่อนในแนวนอนเท่านั้น และเพื่อเลื่อนหน้าไปทางขวาเท่านั้น
pan-up เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการตามค่าเริ่มต้นของการเลื่อนแนวตั้งเท่านั้น และเพื่อเลื่อนหน้าขึ้นเท่านั้น
pan-down เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการตามค่าเริ่มต้นของการเลื่อนแนวตั้งเท่านั้น และเพื่อเลื่อนหน้าลงเท่านั้น
manipulation เบราว์เซอร์ได้รับอนุญาตให้ดำเนินการเลื่อนและซูมเท่านั้น

การจับภาพเคอร์เซอร์

เคยใช้เวลาหลายชั่วโมงในการแก้ไขข้อบกพร่องของmouseupกิจกรรมที่ใช้งานไม่ได้ จนกว่าจะพบว่าเป็นเพราะผู้ใช้ปล่อยปุ่มนอกเป้าหมายการคลิกใช่ไหม หากไม่ โอเค อาจเป็นแค่เรา

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

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

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

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

ขณะเขียนบทความนี้ เหตุการณ์เคอร์เซอร์ได้รับการรองรับใน Internet Explorer 11, Microsoft Edge, Chrome และ Opera รวมถึงได้รับการรองรับบางส่วนใน Firefox คุณดูรายการล่าสุดได้ที่ caniuse.com

คุณสามารถใช้ Pointer Events polyfill เพื่อเติมเต็มช่องว่างได้ หรือจะตรวจสอบการรองรับเบราว์เซอร์ที่รันไทม์ก็ทำได้ง่ายๆ ดังนี้

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

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

โปรดลองใช้แล้วบอกเราว่าคุณคิดเห็นอย่างไร