การไปยังส่วนต่างๆ เชิงพื้นที่ (โฟกัสปุ่มกดทิศทาง)

ฟีเจอร์บนเว็บ: scrollIntoView()

การไปยังส่วนต่างๆ โดยโฟกัสตามทิศทาง (โดยใช้แป้นลูกศรหรือ D-Pad) ช่วยให้ผู้ใช้ที่ใช้แป้นพิมพ์เท่านั้นและผู้ใช้ที่ใช้อุปกรณ์ป้อนข้อมูลทางเลือกสามารถไปยังรายการแบบอินเทอร์แอกทีฟตามเลย์เอาต์ 2 มิติแบบภาพได้ ซึ่งมีประโยชน์อย่างมากสำหรับอินเทอร์เฟซผู้ใช้เชิงพื้นที่ (เช่น แถบสื่อ แผงแดชบอร์ด หรือแคตตาล็อกที่ฝัง) แต่ต้องติดตั้งใช้งานอย่างระมัดระวังเพื่อไม่ให้รบกวนลักษณะการทำงานของเบราว์เซอร์แบบเนทีฟ เช่น การเลื่อนวิวพอร์ต

คู่มือนี้ครอบคลุมการสร้างการนำทางตามทิศทางแบบ 2 มิติที่มีประสิทธิภาพและเข้าถึงได้ภายในวิดเจ็ตแบบผสม


หลักการทางสถาปัตยกรรมที่สำคัญ

เมื่อใช้โฟกัสเชิงพื้นที่ด้วยปุ่มลูกศรที่กำหนดเอง ให้ปฏิบัติตามกฎพื้นฐานต่อไปนี้

  1. จำกัดเฉพาะวิดเจ็ตแบบผสม: อย่าลบล้างแป้นลูกศรในขั้นตอนการแสดงข้อความเอกสารปกติหรือเลย์เอาต์แบบรายการแนวตั้งแบบง่ายที่คาดว่าจะมีการไปยังส่วนต่างๆ ตามลำดับด้วยปุ่ม Tab โดยค่าเริ่มต้น ดูคำแนะนำทั่วไปเกี่ยวกับขั้นตอนการจัดทำเอกสารได้ที่หลักเกณฑ์การเขียนโค้ดสำหรับการช่วยเหลือพิเศษ
  2. คงการเลื่อนขอบเขตแบบเนทีฟไว้: หากไม่มีองค์ประกอบที่โฟกัสได้ในทิศทางที่กด อย่าป้องกันลักษณะการทำงานเริ่มต้นของเบราว์เซอร์ ปล่อยให้เหตุการณ์เกิดการส่งต่อ (Bubble) เพื่อให้เบราว์เซอร์เลื่อนวิวพอร์ตได้โดยตรง การลบล้างแป้นลูกศรจะทำให้การช่วยเหลือพิเศษใช้งานไม่ได้โดยไม่มีเงื่อนไข (WCAG 1.4.10)
  3. ตรวจสอบระดับการมองเห็นและการจัดแนวของโฟกัส: เมื่อโฟกัสเปลี่ยนไป ให้ตรวจสอบว่าองค์ประกอบที่โฟกัสใหม่เลื่อนเข้ามาในมุมมอง (เช่น ใช้ scrollIntoView({ block: 'nearest', inline: 'nearest' })) เพื่อให้มองเห็นได้อย่างสมบูรณ์
  4. ประสานงานกับระบบเลย์เอาต์: กำหนดตำแหน่งผู้สมัครที่โฟกัสจากเรขาคณิตขอบเขตแบบเรียลไทม์ (getBoundingClientRect()) ขณะจัดการการกดแป้นแต่ละครั้ง หลีกเลี่ยงการสันนิษฐานว่าลำดับ DOM หรือโครงสร้างตารางกริดแบบคงที่แสดงถึงเลย์เอาต์ภาพ ดูคำแนะนำเกี่ยวกับเลย์เอาต์สมัยใหม่ได้ที่เลย์เอาต์ CSS และการออกแบบที่ปรับเปลี่ยนตามอุปกรณ์

การจัดการโฟกัสที่เข้าถึงได้ง่าย

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

  • รายการที่ใช้งานอยู่มี tabindex="0"
  • รายการที่ไม่ได้ใช้งานทั้งหมดมี tabindex="-1"
  • เมื่อไปยังส่วนต่างๆ ให้ย้ายโฟกัสโดยใช้โปรแกรมด้วยการเปลี่ยนแอตทริบิวต์ tabindex="0" และเรียกใช้ focus()

การเลือกผู้สมัครแบบเห็นภาพ

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

  • ตัวเลือก: ใช้รายการสืบทอดที่โฟกัสได้ของคอนเทนเนอร์วิดเจ็ตที่ผ่าน checkVisibility() องค์ประกอบที่ซ่อนอยู่สามารถมีสี่เหลี่ยมผืนผ้าขนาดเป็น 0 ที่ (0, 0) และต้องไม่กลายเป็นเป้าหมาย up หรือ left โดยไม่ตั้งใจ
  • เรขาคณิต: อ่าน getBoundingClientRect() สำหรับองค์ประกอบที่โฟกัสและตัวเลือกที่เป็นไปได้ทั้งหมดขณะจัดการการกดแป้น อย่าแคชสี่เหลี่ยมผืนผ้าเหล่านี้ทั่วโลก เนื่องจากเลย์เอาต์อาจเปลี่ยนแปลงได้จากหลายสาเหตุ และการอ่านเรขาคณิตจำนวนเล็กน้อยจะใช้ทรัพยากรน้อยเมื่อจัดการเหตุการณ์สำคัญที่ผู้ใช้กำหนดจังหวะ
  • ตัวกรองทิศทาง: ตัวเลือกจะอยู่ในทิศทางที่กดก็ต่อเมื่อขอบใกล้ของตัวเลือกอยู่ที่หรือเกินขอบไกลขององค์ประกอบปัจจุบันบนแกนนั้น โดยอนุญาตให้มีความคลาดเคลื่อนเล็กน้อยสำหรับการปัดเศษพิกเซลย่อย ตัวอย่างเช่น ผู้สมัครรับเลือกตั้งในrightต้องมีcandidate.left >= current.right - tolerance อย่าเปรียบเทียบจุดศูนย์กลางเพื่อพิจารณาว่าผู้สมัครอยู่ในทิศทางหรือไม่
  • ผู้สมัครที่สอดคล้องกันก่อน: ในบรรดาผู้สมัครที่ผ่านตัวกรองทิศทาง ให้เลือกผู้สมัครที่มีขอบเขตบนแกนตั้งฉากซ้อนทับกับขอบเขตขององค์ประกอบปัจจุบัน เลือกช่องว่างที่เล็กที่สุดบนแกนการนำทาง โดยตัดความสัมพันธ์ด้วยออฟเซ็ตศูนย์กลางที่ตั้งฉากที่เล็กที่สุด ซึ่งจะช่วยให้การไปยังส่วนต่างๆ อยู่ในแถวหรือคอลัมน์ภาพที่อยู่ติดกัน แม้ว่ารายการจะกว้างผิดปกติก็ตาม
  • การให้คะแนนสำรอง: หากไม่มีผู้สมัครที่ทับซ้อนกันบนแกนตั้งฉาก ให้ให้คะแนนผู้สมัครที่เหลือเป็น gap + w * perpendicularCentreOffset และเลือกคะแนนต่ำสุด ใช้น้ำหนัก w >= 2 เป็นจุดเริ่มต้น แล้วปรับแต่งสำหรับเลย์เอาต์ น้ำหนักที่มากขึ้นจะช่วยให้ผู้สมัครที่สอดคล้องกันมากขึ้น
  • ไม่มีตัวเลือกที่ตรงกัน: ไม่แสดงผลลัพธ์ใดๆ เครื่องจัดการเหตุการณ์ต้องไม่เรียกใช้ preventDefault() เพื่อให้เบราว์เซอร์เลื่อนได้ตามปกติ

การใช้งานจริงอาจแตกต่างกันไป แต่ให้ทำการทดสอบทิศทางตาม Edge และใช้ค่ากำหนดการทับซ้อนกันก่อนการสำรองแบบถ่วงน้ำหนัก นี่คือหัวใจสำคัญของอัลกอริทึม หลีกเลี่ยงการนำเสนอแคชหรือน้ำหนักคงที่ว่าถูกต้องเสมอ


การผสานรวมการกดแป้นพิมพ์และการทำงานร่วมกันของการเลื่อน

เราเตอร์เหตุการณ์ต้องป้องกันการเลื่อนวิวพอร์ตมาตรฐานเฉพาะเมื่อมีการเปลี่ยนโฟกัสเชิงพื้นที่ที่ถูกต้อง ดังนี้

  • อย่าขัดจังหวะเหตุการณ์เมื่อมีการกดแป้นกดร่วมค้างไว้ เมื่อ e.defaultPrevented เป็นจริงอยู่แล้ว หรือเมื่อเป้าหมายเหตุการณ์ที่อยู่ด้านในสุดใช้แป้นลูกศรเอง ใช้ e.composedPath()[0] แทนที่จะใช้เพียง e.target เนื่องจาก e.target สามารถกำหนดเป้าหมายใหม่ไปยังโฮสต์เงาได้ การตรวจสอบการควบคุม/การแก้ไขที่ใช้ได้จริงคือ target.matches(':read-write, select, input[type="range"], [role="slider"]') เนื่องจากไม่สามารถตรวจสอบ Shadow Root แบบปิดได้
  • กำหนดขอบเขต Listener ไปยังคอนเทนเนอร์วิดเจ็ต เพื่อให้วิดเจ็ตเชิงพื้นที่หลายรายการอยู่ร่วมกันในหน้าเว็บได้
  • ดำเนินการเฉพาะเมื่อองค์ประกอบที่โฟกัสเป็นตัวเลือกเท่านั้น หากรายการมีการควบคุมที่โฟกัสได้ของตัวเอง ให้กำหนดให้การควบคุมเหล่านั้นเป็นตัวเลือกด้วย หรือปล่อยไว้ตามเดิม อย่าแก้ไขรายการหลักด้วย closest()
  • เมื่อพบผู้สมัคร ให้เรียก preventDefault() อัปเดต tabindex ที่เคลื่อนที่ เรียก focus() และใช้ scrollIntoView({ block: 'nearest', inline: 'nearest' })
  • เมื่อไม่พบตัวเลือกที่ตรงกัน ให้แสดงผลโดยไม่ต้องเรียกใช้ preventDefault() เพื่อให้การเลื่อนแบบเนทีฟดำเนินต่อไป

หลีกเลี่ยงการเพิ่มแป้นพิมพ์ลัดที่มีอักขระเดียว เช่น h/j/k/l หรือ W/A/S/D สำหรับการไปยังส่วนต่างๆ ตามพื้นที่ ปุ่มลูกศรไม่ใช่ปุ่มอักขระ และการคงการเลื่อนแบบเนทีฟไว้เมื่อไม่มีตัวเลือกจะช่วยหลีกเลี่ยงการบล็อกการเคลื่อนไหวของวิวพอร์ตโดยไม่จำเป็น