การสร้างคอมโพเนนต์แบบเลือกหลายรายการ

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

ในโพสต์นี้ ผมจะมาแชร์แนวคิดในการสร้างองค์ประกอบแบบหลายตัวเลือก ลองใช้งาน การสาธิต

การสาธิต

หากชอบวิดีโอ นี่คือโพสต์นี้เวอร์ชัน YouTube

ภาพรวม

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

การโต้ตอบ

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

ภาพหน้าจอเปรียบเทียบที่แสดงธีมมืดบนเดสก์ท็อปพร้อมแถบด้านข้างของช่องทำเครื่องหมาย
เทียบกับ iOS และ Android บนอุปกรณ์เคลื่อนที่ที่มีองค์ประกอบแบบเลือกหลายรายการ

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

สัมผัส

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

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

แป้นพิมพ์และเกมแพด

ด้านล่างนี้คือการสาธิตวิธีใช้ <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 เพราะเป็นสิ่งที่ตัวนับไม่สามารถทำได้

ภาพหน้าจอของโปรแกรมอ่านหน้าจอ MacOS แจ้งจำนวนตัวกรองที่ใช้งานอยู่

ความตื่นเต้นจากการฝัง

อัลกอริทึมตัวนับให้ความรู้สึกที่ดีกับ 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;
}
ภาพหน้าจอแสดงการจัดเรียงของเครื่องหมายถูกกับบรรทัดแรกของข้อความในสถานการณ์การตัดหลายบรรทัด
เล่นต่อใน Codepen นี้

ตารางกริดเคลื่อนไหว

ภาพเคลื่อนไหวของเลย์เอาต์ดำเนินการโดย Isotope ปลั๊กอินที่มีประสิทธิภาพสูงและมีประสิทธิภาพสูงสำหรับการจัดเรียงและตัวกรองแบบอินเทอร์แอกทีฟ

JavaScript

นอกจากจะช่วยจัดตารางแบบอินเทอร์แอกทีฟที่เคลื่อนไหวง่ายแล้ว JavaScript ยังใช้เพื่อขัดเกลาขอบเล็กๆ น้อยๆ อีกด้วย

การทำให้ข้อมูลจากผู้ใช้เป็นปกติ

การออกแบบนี้มีแบบฟอร์มเดียวที่มี 2 วิธีในการป้อนข้อมูล และไม่ได้ทำให้เป็นอนุกรมเหมือนกัน แต่เมื่อใช้ JavaScript เราสามารถปรับข้อมูลให้เป็นมาตรฐานได้

ภาพหน้าจอคอนโซล JavaScript ของ DevTools ซึ่งแสดงเป้าหมายซึ่งเป็นผลลัพธ์ข้อมูลที่เป็นมาตรฐาน

ผมเลือกปรับโครงสร้างข้อมูลองค์ประกอบ <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 รายการ" จนเสร็จสมบูรณ์แล้ว

ภาพหน้าจอของโปรแกรมอ่านหน้าจอ MacOS ที่กำลังประกาศผลลัพธ์

ตอนนี้ระบบจะมอบประสบการณ์การใช้งานเทคโนโลยีความช่วยเหลือพิเศษที่ยอดเยี่ยม ให้กับผู้ใช้ทุกคนไม่ว่าผู้ใช้จะมีปฏิสัมพันธ์อย่างไร

บทสรุป

ตอนนี้คุณก็รู้แล้วว่าตัวเองทำยังไง คุณจะทำอะไรบ้าง‽ 🙂

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

รีมิกซ์ของชุมชน

ยังไม่มีอะไรให้ดูที่นี่