ภาพรวมพื้นฐานของวิธีสร้างคอมโพเนนต์แบบเลือกหลายรายการที่ปรับเปลี่ยนได้ ปรับเปลี่ยนตามอุปกรณ์ และเข้าถึงง่าย เพื่อการจัดเรียงและกรองประสบการณ์ของผู้ใช้
ในโพสต์นี้ ผมจะมาแชร์แนวคิดในการสร้างองค์ประกอบแบบหลายตัวเลือก ลองใช้งาน การสาธิต
หากชอบวิดีโอ นี่คือโพสต์นี้เวอร์ชัน YouTube
ภาพรวม
ผู้ใช้มักเห็นไอเทมและบางครั้งอาจมีรายการจำนวนมาก และในกรณีเหล่านี้ วิธีที่ดีที่สุดในการลดรายการของตัวเองเพื่อป้องกันจำนวนตัวเลือกมากเกินไป บล็อกโพสต์นี้อธิบายถึง UI การกรองเพื่อช่วยลดตัวเลือก ซึ่งทำได้โดยการแสดงแอตทริบิวต์ของรายการที่ผู้ใช้สามารถเลือกหรือยกเลิกการเลือกได้ ซึ่งช่วยลดผลลัพธ์และช่วยลดภาระงานของตัวเลือก
การโต้ตอบ
โดยมีเป้าหมายเพื่อเปิดใช้การข้ามผ่านตัวเลือกตัวกรองอย่างรวดเร็วสำหรับผู้ใช้ทุกคนและประเภทอินพุตที่หลากหลาย โดยจะแสดงด้วยคู่คอมโพเนนต์ที่ปรับเปลี่ยนได้และปรับเปลี่ยนตามอุปกรณ์ได้ แถบด้านข้างแบบดั้งเดิมของช่องทำเครื่องหมายสำหรับเดสก์ท็อป แป้นพิมพ์ และโปรแกรมอ่านหน้าจอ รวมถึง <select
multiple>
สำหรับผู้ใช้แบบสัมผัส
การตัดสินใจนี้ในการใช้การเลือกหลายรายการในตัวสำหรับการแตะ ไม่ใช่สำหรับเดสก์ท็อป จะช่วยประหยัดงานและสร้างงาน แต่ฉันเชื่อว่ามอบประสบการณ์ที่เหมาะสมโดยมีภาระจากโค้ดน้อยกว่าการสร้างประสบการณ์ที่ปรับเปลี่ยนตามอุปกรณ์ทั้งหมดในองค์ประกอบเดียว
สัมผัส
คอมโพเนนต์การสัมผัสช่วยประหยัดพื้นที่และช่วยให้การโต้ตอบของผู้ใช้ในอุปกรณ์เคลื่อนที่ได้แม่นยำขึ้น ซึ่งจะช่วยประหยัดพื้นที่ด้วยการยุบแถบด้านข้างของช่องทำเครื่องหมายทั้งหมดให้เป็นประสบการณ์การแตะแบบวางซ้อนในตัว <select>
ช่วยให้อินพุตมีความแม่นยำด้วยการแสดง
การวางซ้อนการสัมผัสขนาดใหญ่ที่ระบบมีให้
แป้นพิมพ์และเกมแพด
ด้านล่างนี้คือการสาธิตวิธีใช้ <select multiple>
จากแป้นพิมพ์
การเลือกหลายรายการในตัวนี้ไม่สามารถจัดรูปแบบได้และมีเฉพาะการจัดวางแบบกะทัดรัดเท่านั้น ซึ่งไม่เหมาะสำหรับการนำเสนอตัวเลือกจำนวนมาก ทำอย่างไรคุณถึงไม่เห็น ตัวเลือกมากมายในกล่องเล็กๆ นั้นใช่ไหม แม้ว่าคุณจะสามารถเปลี่ยนขนาดได้ แต่ก็ยังใช้งานไม่ได้เท่ากับแถบด้านข้างของช่องทำเครื่องหมาย
Markup
คอมโพเนนต์ทั้ง 2 รายการจะอยู่ในองค์ประกอบ <form>
เดียวกัน ผลลัพธ์ของแบบฟอร์มนี้ ไม่ว่าจะเป็นช่องทำเครื่องหมายหรือการเลือกหลายรายการ จะสังเกตเห็นได้และใช้เพื่อกรองตารางกริดนั้น แต่ก็สามารถส่งไปยังเซิร์ฟเวอร์ได้เช่นกัน
<form>
</form>
คอมโพเนนต์ช่องทำเครื่องหมาย
กลุ่มช่องทำเครื่องหมายควรรวมอยู่ในองค์ประกอบ <fieldset>
และกำหนด <legend>
เมื่อมีการจัดโครงสร้าง HTML ด้วยวิธีนี้ โปรแกรมอ่านหน้าจอและ FormData จะเข้าใจความสัมพันธ์ขององค์ประกอบโดยอัตโนมัติ
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
เมื่อจัดกลุ่มแล้ว ให้เพิ่ม <label>
และ <input type="checkbox">
สำหรับตัวกรองแต่ละรายการ ผมเลือกที่จะรวม URL ของฉันไว้ใน <div>
เพื่อให้พร็อพเพอร์ตี้ gap
ของ CSS สามารถเว้นระยะห่างเท่าๆ กันและคงความสอดคล้องเมื่อป้ายกำกับอยู่ในหลายบรรทัด
<form>
<fieldset>
<legend>New</legend>
<div>
<input type="checkbox" id="last 30 days" name="new" value="last 30 days">
<label for="last 30 days">Last 30 Days</label>
</div>
<div>
<input type="checkbox" id="last 6 months" name="new" value="last 6 months">
<label for="last 6 months">Last 6 Months</label>
</div>
</fieldset>
</form>
คอมโพเนนต์ <select multiple>
ฟีเจอร์ขององค์ประกอบ <select>
ที่ไม่ค่อยได้ใช้คือ multiple
เมื่อใช้แอตทริบิวต์กับองค์ประกอบ <select>
ผู้ใช้จะเลือกหลายรายการจากรายการได้ เหมือนกับการเปลี่ยนการโต้ตอบ
จากรายการวิทยุเป็นรายการช่องทำเครื่องหมาย
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
หากต้องการติดป้ายกำกับและสร้างกลุ่มภายใน <select>
ให้ใช้องค์ประกอบ <optgroup>
และกำหนดแอตทริบิวต์และค่า label
องค์ประกอบและค่าแอตทริบิวต์นี้คล้ายกับองค์ประกอบ <fieldset>
และ <legend>
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
ตอนนี้ให้เพิ่มองค์ประกอบ <option>
สำหรับตัวกรอง
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
<option value="last 30 days">Last 30 Days</option>
<option value="last 6 months">Last 6 Months</option>
</optgroup>
</select>
</form>
การติดตามอินพุตด้วยตัวนับเพื่อแจ้งเทคโนโลยีความช่วยเหลือพิเศษ
ระบบนำเทคนิคบทบาท
สถานะ
มาใช้ในประสบการณ์ของผู้ใช้นี้ เพื่อติดตามและคงการนับของตัวกรองสำหรับโปรแกรมอ่านหน้าจอและเทคโนโลยีความช่วยเหลืออื่นๆ วิดีโอ YouTube จะแสดง
ฟีเจอร์นี้ให้ดู การผสานรวมจะเริ่มด้วย HTML และแอตทริบิวต์ role="status"
<div role="status" class="sr-only" id="applied-filters"></div>
องค์ประกอบนี้จะอ่านออกเสียงการเปลี่ยนแปลงที่เกิดขึ้นกับเนื้อหา เราสามารถอัปเดตเนื้อหาด้วยตัวนับ CSS เมื่อผู้ใช้โต้ตอบกับช่องทำเครื่องหมาย ในการทำเช่นนั้น ก่อนอื่นเราต้องสร้างตัวนับที่มีชื่อในองค์ประกอบระดับบนสุดของอินพุตและองค์ประกอบสถานะ
aside {
counter-reset: filters;
}
โดยค่าเริ่มต้น การนับจะเป็น 0
ซึ่งยอดเยี่ยมมาก ค่าเริ่มต้นในการออกแบบนี้จะไม่มี :checked
เลย
จากนั้น หากต้องการเพิ่มตัวนับที่สร้างขึ้นใหม่ เราจะกําหนดเป้าหมายย่อยขององค์ประกอบ <aside>
ที่เป็น :checked
เมื่อผู้ใช้เปลี่ยนสถานะของอินพุต ตัวนับ filters
จะนับรวมกัน
aside :checked {
counter-increment: filters;
}
ตอนนี้ CSS รับรู้ถึงผลการนับทั่วไปของ UI ช่องทำเครื่องหมายและองค์ประกอบบทบาทสถานะว่างเปล่าและกำลังรอค่า เนื่องจาก CSS รักษาการนับในหน่วยความจำ ฟังก์ชัน counter()
จึงอนุญาตให้เข้าถึงค่าจากเนื้อหาองค์ประกอบเทียม ดังนี้
aside #applied-filters::before {
content: counter(filters) " filters ";
}
ตอนนี้ HTML สำหรับองค์ประกอบบทบาทสถานะจะประกาศ "ตัวกรอง 2 รายการ " ต่อโปรแกรมอ่านหน้าจอ นี่เป็นการเริ่มต้นที่ดี แต่เราสามารถปรับปรุงให้ดีขึ้น เช่น แชร์ผลการนับผลลัพธ์ที่ตัวกรองอัปเดต เราจะทำงานนี้จาก JavaScript เพราะเป็นสิ่งที่ตัวนับไม่สามารถทำได้
ความตื่นเต้นจากการฝัง
อัลกอริทึมตัวนับให้ความรู้สึกที่ดีกับ CSS การซ้อน-1 เนื่องจากฉันใส่ตรรกะทั้งหมดไว้ในบล็อกเดียวได้ พกพาสะดวก พร้อมรวมศูนย์สำหรับการอ่านและอัปเดต
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
เลย์เอาต์
ส่วนนี้จะอธิบายเลย์เอาต์ระหว่างคอมโพเนนต์ 2 รายการ รูปแบบการออกแบบส่วนใหญ่เป็นของคอมโพเนนต์ช่องทำเครื่องหมายบนเดสก์ท็อป
แบบฟอร์ม
แบบฟอร์มจะมีความกว้างสูงสุด 30 อักขระซึ่งจำเป็นต่อการกำหนดความกว้างของเส้นออปติคัลสำหรับป้ายกำกับตัวกรองแต่ละรายการ เพื่อให้ผู้ใช้อ่านได้ง่ายและเข้าใจได้ง่ายที่สุด แบบฟอร์มใช้เลย์เอาต์แบบตารางกริดและพร็อพเพอร์ตี้ gap
เพื่อเว้นระยะห่างระหว่างชุดฟิลด์
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
องค์ประกอบ <select>
ทั้งรายการป้ายกำกับและช่องทำเครื่องหมายใช้พื้นที่ในอุปกรณ์เคลื่อนที่มากเกินไป ดังนั้น เลย์เอาต์จะตรวจสอบเพื่อดูอุปกรณ์ชี้ตำแหน่งหลักของผู้ใช้เพื่อเปลี่ยนประสบการณ์ในการสัมผัส
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
ค่า coarse
บ่งชี้ว่าผู้ใช้จะโต้ตอบกับหน้าจอที่มีความแม่นยำสูงกับอุปกรณ์อินพุตหลักไม่ได้ ในอุปกรณ์เคลื่อนที่ ค่าตัวชี้มักเป็น coarse
เนื่องจากการโต้ตอบหลักคือการแตะ ในอุปกรณ์เดสก์ท็อป ค่าตัวชี้มักเป็น fine
เนื่องจากเป็นเรื่องปกติที่มีการเชื่อมต่อเมาส์หรืออุปกรณ์อินพุตที่มีความแม่นยำสูงอื่นๆ
ชุดข้อมูล
การจัดรูปแบบและเลย์เอาต์เริ่มต้นของ <fieldset>
ที่มี <legend>
จะไม่ซ้ำกัน ดังนี้
โดยปกติ เพื่อเว้นระยะห่างระหว่างองค์ประกอบย่อย ฉันจะใช้พร็อพเพอร์ตี้ gap
แต่การวางตำแหน่งที่ไม่ซ้ำกันของ <legend>
ทำให้สร้างชุดเด็กที่มีระยะห่างเท่ากันได้ยาก ระบบจะใช้ตัวเลือกข้างเคียง และ margin-block-start
แทน gap
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
การทำเช่นนี้จะข้ามไม่ให้ <legend>
ปรับพื้นที่โดยการกำหนดเป้าหมายเฉพาะเด็ก <div>
คน
ป้ายกำกับตัวกรองและช่องทำเครื่องหมาย
ในฐานะระดับย่อยโดยตรงของ <fieldset>
และอยู่ในความกว้างสูงสุดของ 30ch
ของแบบฟอร์ม ข้อความในป้ายกำกับอาจตัดข้อความได้หากยาวเกินไป การตัดข้อความเป็นเรื่องดี แต่การวางตำแหน่งระหว่างข้อความและช่องทำเครื่องหมายไม่ถูกต้อง Flexbox เหมาะสำหรับกรณีนี้
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
![ภาพหน้าจอแสดงการจัดเรียงของเครื่องหมายถูกกับบรรทัดแรกของข้อความในสถานการณ์การตัดหลายบรรทัด](https://web.dev/static/articles/building/a-multi-select-component/image/screenshot-showing-the-c-b135285ef7d8a.png?authuser=5&hl=th)
ตารางกริดเคลื่อนไหว
ภาพเคลื่อนไหวของเลย์เอาต์ดำเนินการโดย Isotope ปลั๊กอินที่มีประสิทธิภาพสูงและมีประสิทธิภาพสูงสำหรับการจัดเรียงและตัวกรองแบบอินเทอร์แอกทีฟ
JavaScript
นอกจากจะช่วยจัดตารางแบบอินเทอร์แอกทีฟที่เคลื่อนไหวง่ายแล้ว JavaScript ยังใช้เพื่อขัดเกลาขอบเล็กๆ น้อยๆ อีกด้วย
การทำให้ข้อมูลจากผู้ใช้เป็นปกติ
การออกแบบนี้มีแบบฟอร์มเดียวที่มี 2 วิธีในการป้อนข้อมูล และไม่ได้ทำให้เป็นอนุกรมเหมือนกัน แต่เมื่อใช้ JavaScript เราสามารถปรับข้อมูลให้เป็นมาตรฐานได้
ผมเลือกปรับโครงสร้างข้อมูลองค์ประกอบ <select>
ให้สอดคล้องกับโครงสร้างช่องทำเครื่องหมายที่จัดกลุ่มไว้ ซึ่งทำได้ด้วยการเพิ่ม Listener เหตุการณ์ input
ลงในองค์ประกอบ <select>
ซึ่งจุดนั้นจะได้รับการแมป selectedOptions
document.querySelector('select').addEventListener('input', event => {
// make selectedOptions iterable then reduce a new array object
let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
// parent optgroup label and option value are added to the reduce aggregator
data.push([opt.parentElement.label.toLowerCase(), opt.value])
return data
}, [])
})
ตอนนี้คุณสามารถส่งแบบฟอร์มได้อย่างปลอดภัยแล้ว หรือในกรณีของการสาธิตนี้ ให้แนะนำ Isotope ว่าควรกรองอะไร
การเสร็จสิ้นองค์ประกอบบทบาทสถานะ
องค์ประกอบจะนับเฉพาะและแจ้งจำนวนตัวกรองตามการโต้ตอบในช่องทำเครื่องหมาย แต่ฉันคิดว่าการแชร์จำนวนผลลัพธ์เพิ่มเติมและตรวจสอบว่ามีการนับตัวเลือกองค์ประกอบ <select>
ด้วย
ตัวเลือกองค์ประกอบ <select>
รายการที่แสดงในcounter()
ในส่วนการปรับข้อมูลให้สอดคล้องตามมาตรฐาน มีการสร้าง Listener ในอินพุตอยู่แล้ว เมื่อสิ้นสุดฟังก์ชันนี้ คุณจะเห็นจำนวนตัวกรองที่เลือกและจำนวนผลลัพธ์ของตัวกรองเหล่านั้น สามารถส่งค่าไปยังองค์ประกอบบทบาทสถานะได้ แบบนี้
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
ผลลัพธ์สะท้อนให้เห็นในองค์ประกอบ role="status"
:checked
มีวิธีในตัวในการส่งจำนวนตัวกรองที่เลือกไปยังองค์ประกอบบทบาทสถานะ แต่มองไม่เห็นจำนวนผลลัพธ์ที่กรอง
JavaScript จะคอยดูการโต้ตอบกับช่องทำเครื่องหมาย และหลังจากกรองตารางกริดแล้ว ให้เพิ่ม textContent
เช่นเดียวกับองค์ประกอบ <select>
document
.querySelector('aside form')
.addEventListener('input', e => {
// isotope demo code
let filterResults = IsotopeGrid.getFilteredItemElements().length
document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})
ทั้งหมดนี้เป็นการดำเนินการประกาศ "ตัวกรอง 2 รายการซึ่งให้ผลลัพธ์ 25 รายการ" จนเสร็จสมบูรณ์แล้ว
ตอนนี้ระบบจะมอบประสบการณ์การใช้งานเทคโนโลยีความช่วยเหลือพิเศษที่ยอดเยี่ยม ให้กับผู้ใช้ทุกคนไม่ว่าผู้ใช้จะมีปฏิสัมพันธ์อย่างไร
บทสรุป
ตอนนี้คุณก็รู้แล้วว่าตัวเองทำยังไง คุณจะทำอะไรบ้าง‽ 🙂
มาลองเปลี่ยนแนวทางของเราและเรียนรู้วิธีทั้งหมดเพื่อสร้างเว็บกันเถอะ สร้างเดโม ลิงก์ทวีตฉัน แล้วฉันจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
ยังไม่มีอะไรให้ดูที่นี่