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

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

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

การสาธิต

หากต้องการดูวิดีโอ คุณสามารถใช้โพสต์นี้ในเวอร์ชัน 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"> สําหรับตัวกรองแต่ละรายการ เราเลือกที่จะตัดข้อความใน <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>

ภาพหน้าจอที่มีการวางซ้อนข้อมูลสําหรับคําอธิบายประกอบและองค์ประกอบ fieldset ซึ่งแสดงสีและชื่อองค์ประกอบ

คอมโพเนนต์ <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() จึงอนุญาตให้เข้าถึงค่าจากเนื้อหา pseudo Element

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

ตอนนี้ HTML สำหรับองค์ประกอบบทบาทสถานะจะอ่านออกเสียงว่า "ตัวกรอง 2 รายการ" ให้กับโปรแกรมอ่านหน้าจอ นี่เป็นจุดเริ่มต้นที่ดี แต่เราทำได้ดีกว่านี้ เช่น แชร์จํานวนผลการค้นหาที่ตัวกรองอัปเดต เราจะทํางานนี้จาก JavaScript เนื่องจากอยู่นอกเหนือความสามารถของตัวนับ

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

ความตื่นเต้นของซ้อนกัน

อัลกอริทึมตัวนับจะทำงานได้ดีกับการซ้อน-1 ของ CSS เนื่องจากฉันสามารถใส่ตรรกะทั้งหมดไว้ในบล็อกเดียว พกพาสะดวกและรวมไว้ในที่เดียวสำหรับการอ่านและการอัปเดต

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> ที่ไม่ซ้ำกันทําให้สร้างชุดองค์ประกอบย่อยที่เว้นวรรคเท่าๆ กันได้ยาก แทนที่จะใช้ gap ให้ใช้ตัวเลือกและ margin-block-start ของรายการพี่น้องที่อยู่ติดกัน

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 ในเครื่องมือสําหรับนักพัฒนาซอฟต์แวร์ซึ่งแสดงเป้าหมายและผลลัพธ์ของข้อมูลที่แปลงเป็นมาตรฐาน

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

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 ที่อ่านออกเสียงผลการค้นหา

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

บทสรุป

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

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

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

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