技术咨询:采用<ul><li>替代<select>实现网站导航与筛选
嘿,这事儿我熟!把传统的<select>类别选择改成<ul><li>的导航/筛选样式,既能提升视觉体验,又能保持原有的功能完整性,我给你捋捋具体实现方案和要注意的细节~
1. 基础结构与JS交互
首先用<ul>作为容器,每个<li>对应原来的一个<option>。为了和原有的JS跳转逻辑对齐,我们可以给每个<li>绑定点击事件,触发页面跳转;同时用<a>标签做兜底,无JS时也能正常工作。
示例代码:
<!-- 容器加上类名方便样式和JS操作,aria-label提升可访问性 --> <ul class="category-nav" aria-label="类别导航"> <!-- 初始选中项加上active类 --> <li class="active"> <a href="/category/tech">科技</a> </li> <li> <a href="/category/life">生活</a> </li> <li> <a href="/category/entertainment">娱乐</a> </li> </ul> <script> const categoryItems = document.querySelectorAll('.category-nav li'); categoryItems.forEach(item => { item.addEventListener('click', (e) => { // 先移除所有active类 categoryItems.forEach(i => i.classList.remove('active')); // 给当前点击项加上active类 item.classList.add('active'); // 触发跳转(这里直接用a标签的href,也可以自定义逻辑) const targetUrl = item.querySelector('a').getAttribute('href'); window.location.href = targetUrl; }); }); </script>
2. 无JS降级方案
原来的<select>依赖<submit>按钮降级,改成<ul><li>后,我们用原生<a>标签就能实现更自然的无JS体验——用户点击直接跳转,完全不需要额外的按钮,比原来的方案更友好!
上面的示例代码已经包含了这个逻辑:当JS禁用时,<a>标签会直接触发页面跳转,完全不影响核心功能。
3. 初始选中与回发状态保持
和原有的<select>逻辑一致,后端渲染页面时,判断当前页面对应的类别,给对应的<li>加上active类即可。比如用后端模板(以PHP为例):
<ul class="category-nav" aria-label="类别导航"> <?php // 后端获取类别列表和当前选中的类别slug $categories = [ ['slug' => 'tech', 'name' => '科技'], ['slug' => 'life', 'name' => '生活'], ['slug' => 'entertainment', 'name' => '娱乐'], ]; $currentCategory = 'tech'; // 假设当前页面是科技类别 ?> <?php foreach($categories as $cat): ?> <li class="<?php echo ($currentCategory === $cat['slug']) ? 'active' : ''; ?>"> <a href="/category/<?php echo $cat['slug']; ?>"><?php echo $cat['name']; ?></a> </li> <?php endforeach; ?> </ul>
这样不管是首次加载还是回发跳转后,页面都会自动高亮当前选中的类别。
可访问性(重中之重):原生
<select>自带键盘导航(上下键选择、回车触发),改成<ul><li>后需要手动补全这部分功能,确保屏幕阅读器和键盘用户能正常使用:- 给
<ul>添加aria-label或aria-labelledby,让屏幕阅读器识别这是类别导航; - 实现键盘交互:支持Tab键聚焦到导航容器,上下箭头切换选中项,Enter/Space键触发跳转,示例逻辑如下:
const navList = document.querySelector('.category-nav'); const items = Array.from(navList.querySelectorAll('li')); let currentIndex = items.findIndex(item => item.classList.contains('active')) || 0; navList.addEventListener('keydown', (e) => { switch(e.key) { case 'ArrowDown': e.preventDefault(); currentIndex = (currentIndex + 1) % items.length; items[currentIndex].querySelector('a').focus(); break; case 'ArrowUp': e.preventDefault(); currentIndex = (currentIndex - 1 + items.length) % items.length; items[currentIndex].querySelector('a').focus(); break; case 'Enter': case ' ': e.preventDefault(); items[currentIndex].querySelector('a').click(); break; } }); - 确保
<a>标签的焦点样式可见,不要用outline: none去掉默认焦点,除非自定义了清晰的焦点样式。
- 给
样式一致性:
- 给
.active类设置明显的视觉标识(比如背景色、下划线、字体加粗),和原<select>的选中状态体验对齐; - 给
<li>添加hover和focus状态的样式,提升交互反馈; - 注意排版:桌面端可以横向排列,移动端可以根据空间调整为纵向或折叠下拉。
- 给
响应式适配:原生
<select>在移动端是紧凑的下拉控件,改成<ul><li>后,小屏幕下可能会占用过多空间。可以用媒体查询配合JS实现折叠菜单:小屏幕下只显示当前选中的类别,点击后展开所有选项,兼顾体验和空间效率。维护性:尽量用后端模板循环生成
<li>项,不要硬写每个类别,后续新增或修改类别时只需更新后端数据即可,减少重复代码。性能考量:如果是单页应用(SPA),可以改成无刷新跳转(比如用
history.pushState),但如果是传统多页应用,保持原有的页面跳转逻辑就足够,不需要额外优化。
内容的提问来源于stack exchange,提问作者Scuzzlebutt

