昨天我們完成了劇集卡片的 HTML 結構,今天就來把「篩選區」變得更好用、更好看!雖然看起來只是一些小互動,但這會大幅提升使用者體驗,之後接上真正的篩選邏輯也更順手。
把「篩選區」替換成下面這段;在它後面新增一段「已選標籤列」。
在你的
<section class="filters">…</section>
位置,改成:
<!-- 篩選按鈕群組(Day 6) -->
<section class="filters" aria-label="類型篩選">
<div class="filter-group" role="group" aria-label="類型">
<button class="filter-btn" data-genre="Romance" aria-pressed="false">愛情</button>
<button class="filter-btn" data-genre="Comedy" aria-pressed="false">喜劇</button>
<button class="filter-btn" data-genre="Mystery" aria-pressed="false">懸疑</button>
<button class="filter-btn" data-genre="Action" aria-pressed="false">動作</button>
</div>
<div class="filter-actions">
<button class="btn btn-clear" id="btnClear" type="button" aria-disabled="true">清除篩選</button>
</div>
</section>
<!-- 已選標籤列(動態顯示) -->
<section class="selected-bar" aria-live="polite" aria-atomic="true">
<span class="label">已選:</span>
<div id="selectedTags" class="chips"></div>
</section>
style.css
末尾加入樣式把下面貼到 style.css
最底部(沿用 Day 5 的設計系統變數)。
/* Day 6:篩選 UX 強化 */
.filter-group{ display: flex; gap: 10px; flex-wrap: wrap; }
.filter-actions{ margin-left: auto; }
.btn-clear[aria-disabled="true"]{ opacity: .45; pointer-events: none; }
/* 已選標籤列 */
.selected-bar{
display: flex; align-items: center; gap: 10px;
margin: 8px 0 16px;
}
.selected-bar .label{ color: var(--muted); font-size: .95rem; }
.chips{ display: flex; gap: 8px; flex-wrap: wrap; }
.chip{
display: inline-flex; align-items: center; gap: 6px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
padding: 6px 10px;
font-size: .92rem;
}
.chip .remove{
border: none; background: transparent; cursor: pointer;
color: var(--muted); font-size: 16px; line-height: 1;
}
.chip .remove:hover{ color: var(--text); }
/* 無障礙:只有圖示時提供隱形文字 */
.visually-hidden{
position: absolute !important; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
white-space: nowrap; border: 0;
}
/* 已選狀態沿用 Day 5 的 .filter-btn.active */
在首頁檔 </main>
前,加入這段 <script>
。
這只是 UI 同步,還不做真正的卡片過濾。
<script>
$(function(){
const $buttons = $('.filter-btn');
const $chips = $('#selectedTags');
const $clear = $('#btnClear');
const labelMap = { Romance:'愛情', Comedy:'喜劇', Mystery:'懸疑', Action:'動作' };
function updateClearState(){
const any = $('.filter-btn.active').length > 0;
$clear.attr('aria-disabled', any ? 'false' : 'true');
}
function addChip(genre){
const text = labelMap[genre] || genre;
if ($chips.find(`[data-genre="${genre}"]`).length) return;
$chips.append(`
<span class="chip" data-genre="${genre}">
${text}
<button class="remove" type="button" aria-label="移除 ${text}">
×<span class="visually-hidden"> 移除 ${text}</span>
</button>
</span>
`);
}
function removeChip(genre){
$chips.find(`[data-genre="${genre}"]`).remove();
}
// 點擊切換狀態(支援鍵盤 Space/Enter)
$(document).on('click', '.filter-btn', function(){
const $btn = $(this);
const genre = $btn.data('genre');
const active = !$btn.hasClass('active');
$btn.toggleClass('active', active).attr('aria-pressed', active);
active ? addChip(genre) : removeChip(genre);
updateClearState();
// TODO:Day 8 這裡接「實際過濾卡片」
}).on('keydown', '.filter-btn', function(e){
if (e.key === ' ' || e.key === 'Enter'){ e.preventDefault(); $(this).click(); }
});
// 移除單一 chip
$(document).on('click', '.chip .remove', function(){
const $chip = $(this).closest('.chip');
const genre = $chip.data('genre');
$chip.remove();
const $btn = $buttons.filter(`[data-genre="${genre}"]`);
$btn.removeClass('active').attr('aria-pressed', 'false');
updateClearState();
});
// 一鍵清除
$clear.on('click', function(){
if ($clear.attr('aria-disabled') === 'true') return;
$buttons.removeClass('active').attr('aria-pressed', 'false');
$chips.empty();
updateClearState();
});
// 初始狀態
updateClearState();
});
</script>