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

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.'));

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

คุณหยุดไม่ให้เบราว์เซอร์ควบคุมได้ด้วยพร็อพเพอร์ตี้ 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!'));

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

ขณะที่เขียน Pointer Event ใช้ได้ใน Internet Explorer 11, Microsoft Edge, Chrome และ Opera และ Firefox รองรับบางส่วน ดูรายการล่าสุดได้ที่ caniuse.com

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

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

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

ดังนั้น ไปดูกันเลย และบอกให้เราทราบว่าคุณคิดอย่างไร