多选

此模式展示了如何构建响应式、自适应、可访问的多选组件,用于对用户体验进行排序和过滤。

完整文章 · YouTube 上的视频 · GitHub 上的来源

<main>
 
<header>
   
<h1>Lighting</h1>
   
<small>Find your perfect light</small>
 
</header>
 
<aside>
   
<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>
       
<optgroup label="Lamps">
         
<option value="table lamps">Table Lamps</option>
         
<option value="desk lamps">Desk Lamps</option>
         
<option value="floor lamps">Floor Lamps</option>
       
</optgroup>
       
<optgroup label="Ceiling">
         
<option value="chandeliers">Chandeliers</option>
         
<option value="pendant">Pendant</option>
         
<option value="flush">Flush</option>
         
<option value="fans">Fans</option>
       
</optgroup>
       
<optgroup label="By Room">
         
<option value="bedroom">Bedroom</option>
         
<option value="dining room">Dining Room</option>
         
<option value="kitchen">Kitchen</option>
         
<option value="living room">Living Room</option>
         
<option value="bathroom">Bathroom</option>
         
<option value="entryway">Entryway</option>
         
<option value="outdoor">Outdoor</option>
       
</optgroup>
       
<optgroup label="Kids">
         
<option value="lamps">Lamps</option>
         
<option value="night lights">Night Lights</option>
         
<option value="ceiling">Ceiling</option>
       
</optgroup>
     
</select>
     
     
<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>
     
<fieldset>
       
<legend>Lamps</legend>
       
<div>
         
<input type="checkbox" id="table-lamps" name="lamps" value="table lamps">
         
<label for="table-lamps">Table Lamps</label>
       
</div>
       
<div>
         
<input type="checkbox" id="desk-lamps" name="lamps" value="desk lamps">
         
<label for="desk-lamps">Desk Lamps</label>
       
</div>
       
<div>
         
<input type="checkbox" id="floor-lamps" name="lamps" value="floor lamps">
         
<label for="floor-lamps">Floor Lamps</label>
       
</div>
     
</fieldset>
     
<fieldset>
       
<legend>Ceiling</legend>
       
<div>
         
<input type="checkbox" id="chandeliers" name="ceiling" value="chandeliers">
         
<label for="chandeliers">Chandeliers</label>
       
</div>
       
<div>
         
<input type="checkbox" id="pendant" name="ceiling" value="pendant">
         
<label for="pendant">Pendant</label>
       
</div>
       
<div>
         
<input type="checkbox" id="flush" name="ceiling" value="flush">
         
<label for="flush">Flush</label>
       
</div>
       
<div>
         
<input type="checkbox" id="fans" name="ceiling" value="fans">
         
<label for="fans">Fans</label>
       
</div>
     
</fieldset>
     
<fieldset>
       
<legend>By Room</legend>
       
<div>
         
<input type="checkbox" id="bedroom" name="by room" value="bedroom">
         
<label for="bedroom">Bedroom</label>
       
</div>
       
<div>
         
<input type="checkbox" id="dining-room" name="by room" value="dining room">
         
<label for="dining-room">Dining Room</label>
       
</div>
       
<div>
         
<input type="checkbox" id="kitchen" name="by room" value="kitchen">
         
<label for="kitchen">Kitchen</label>
       
</div>
       
<div>
         
<input type="checkbox" id="living-room" name="by room" value="living room">
         
<label for="living-room">Living Room</label>
       
</div>
       
<div>
         
<input type="checkbox" id="bathroom" name="by room" value="bathroom">
         
<label for="bathroom">Bathroom</label>
       
</div>
       
<div>
         
<input type="checkbox" id="entryway" name="by room" value="entryway">
         
<label for="entryway">Entryway</label>
       
</div>
       
<div>
         
<input type="checkbox" id="outdoor" name="by room" value="outdoor">
         
<label for="outdoor">Outdoor</label>
       
</div>
     
</fieldset>
     
<fieldset>
       
<legend>Kids</legend>
       
<div>
         
<input type="checkbox" id="lamps" name="kids" value="lamps">
         
<label for="lamps">Lamps</label>
       
</div>
       
<div>
         
<input type="checkbox" id="night-lights" name="kids" value="night lights">
         
<label for="night-lights">Night Lights</label>
       
</div>
       
<div>
         
<input type="checkbox" id="ceiling" name="kids" value="ceiling">
         
<label for="ceiling">Ceiling</label>
       
</div>
     
</fieldset>
   
</form>
   
<div role="status" class="sr-only" id="applied-filters"></div>
 
</aside>
 
<article>
   
<span class="last-30-days table-lamps"></span>
   
<span class="last-6-months desk-lamps"></span>
   
<span class="floor-lamps"></span>
   
<span class="last-6-months chandeliers"></span>
   
<span class="pendant last-6-months"></span>
   
<span class="flush fans"></span>
   
<span class="fans pendant table-lamps"></span>
   
<span class="bedroom"></span>
   
<span class="dining-room last-30-days chandeliers"></span>
   
<span class="kitchen lamps"></span>
   
<span class="living-room"></span>
   
<span class="bathroom living-room chandeliers desk-lamps"></span>
   
<span class="bathroom table-lamps desk-lamps"></span>
   
<span class="entryway last-30-days"></span>
   
<span class="outdoor desk-lamps"></span>
   
<span class="lamps last-30-days"></span>
   
<span class="night-lights table-lamps"></span>
   
<span class="ceiling last-30-days"></span>
   
<span class="floor-lamps table-lamps"></span>
   
<span class="floor-lamps last-6-months"></span>
   
<span class="dining-room last-30-days chandeliers"></span>
   
<span class="kitchen lamps"></span>
   
<span class="living-room"></span>
   
<span class="bathroom living-room chandeliers desk-lamps"></span>
 
</article>
</main>

        main
{
 
display: grid;
 
grid-template-columns: max-content 1fr;
 
gap: 5vmin;
 
align-items: flex-start;

 
& > header {
   
grid-column: 1 / -1;
 
}

 
@media (orientation: portrait) {
   
grid-template-columns: 1fr;
 
}

 
@media (--useSelect) {
   
& > article {
     
grid-row: 3;
     
grid-column: 1 / -1;
   
}
 
}
}

article
{
 
--size: min(300px, calc(25% - 2ch));
 
margin: -1ch;

 
& > span {
   
will-change: transform;
   
background: hsl(0 0% 50% / 25%);
   
border-radius: 10px;
   
inline-size: var(--size);
   
block-size: 15ch;
   
margin: 1ch;

   
@media (orientation: portrait) {
     
--size: calc(50% - 2ch);
   
}

   
@supports (aspect-ratio: 1) {
     
block-size: auto;
     
aspect-ratio: 1;
   
}
 
}
}

header
{
 
display: grid;
 
gap: 1ch;
}

aside
{
 
counter-reset: filters;

 
& :checked {
   
counter-increment: filters;
 
}

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

fieldset:first-of-type {
 
margin-block-start: -5px;
}

[role="status"] {
 
@media (--useSelect) {
   
display: none;
 
}
}

.sr-only {
 
inline-size: 0;
 
block-size: 0;
 
overflow: hidden;
}
       

       
import 'https://unpkg.com/isotope-layout@3.0.6/dist/isotope.pkgd.min.js'

const IsotopeGrid = new Isotope( 'article', {
  itemSelector
: 'span',
  layoutMode
: 'fitRows',
  percentPosition
: true
})
 
const filterGrid = query => {
 
const { matches:motionOK } = window.matchMedia(
   
'(prefers-reduced-motion: no-preference)'
 
)
 
 
IsotopeGrid.arrange({
    filter
: query,
    stagger
: 25,
    transitionDuration
: motionOK ? '0.4s' : 0,
 
})
}

// takes a  watcher
document
.querySelector('select').addEventListener('input', e => {
  let selectData
= prepareSelectOptions(e.target)
  console
.warn('Multiselect', selectData)

 
// DEMO
 
// isotope query assembly from checkbox selections
  let query
= selectData.reduce((query, val) => {
    query
.push('.' + val[1].split(' ').join('-'))
   
return query
 
}, []).join(',')

  filterGrid
(query)

 
// update for assistive technology
  let statusRoleElement
= document.querySelector('#applied-filters')
  let filterResults
= IsotopeGrid.getFilteredItemElements().length

  statusRoleElement
.style.counterSet = selectData.length
  statusRoleElement
.textContent = " giving " + filterResults + " results"
})

document
 
.querySelector('aside form')
 
.addEventListener('input', e => {
   
if (e.target.nodeName === 'SELECT') return
     
   
const formData = new FormData(document.querySelector('form'))
    console
.warn('Checkboxes', Array.from(formData.entries()))

   
// DEMO
   
// isotope query assembly from checkbox selections
    let query
= Array.from(formData.values()).reduce((query, val) => {
      query
.push('.' + val.split(' ').join('-'))
     
return query
   
}, []).join(',')

    filterGrid
(query)

    document
.querySelector('#applied-filters').textContent = " giving " + IsotopeGrid.getFilteredItemElements().length + " results"
 
})