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

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

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

การสาธิต

หากต้องการดูวิดีโอ โปรดดูโพสต์นี้ใน YouTube

ภาพรวม

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

การโต้ตอบ

เป้าหมายคือการช่วยให้ผู้ใช้ทุกคนและอินพุตประเภทต่างๆ ของผู้ใช้สามารถเลื่อนดูตัวเลือกตัวกรองได้อย่างรวดเร็ว ซึ่งจะมาพร้อมกับคอมโพเนนต์ 2 อย่างที่ปรับเปลี่ยนได้และตอบสนองได้ แถบด้านข้างแบบเดิมที่มีช่องทำเครื่องหมายสำหรับเดสก์ท็อป แป้นพิมพ์ และโปรแกรมอ่านหน้าจอ รวมถึง<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() จึงอนุญาตให้เข้าถึงค่าจากเนื้อหาองค์ประกอบเสมือนได้

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

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

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

ความตื่นเต้นในการทำรัง

อัลกอริทึมตัวนับทำงานได้ดีกับ CSS nesting-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> จะมีลักษณะเฉพาะดังนี้

ภาพหน้าจอของรูปแบบเริ่มต้นสำหรับ 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 ใน DevTools ซึ่ง
  แสดงเป้าหมายและผลลัพธ์ของข้อมูลที่ปรับให้เป็นมาตรฐาน

ฉันเลือกที่จะจัดโครงสร้างข้อมูลขององค์ประกอบ <select> ให้สอดคล้องกับโครงสร้างของช่องทําเครื่องหมายที่จัดกลุ่ม โดยจะมีการเพิ่ม input Listener เหตุการณ์ลงในองค์ประกอบ <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 ที่ประกาศผลการค้นหา

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

บทสรุป

ตอนนี้คุณรู้วิธีที่ฉันใช้แล้ว คุณจะทำอย่างไร 🙂

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

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

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