可行但需自行处理焦点管理、键盘导航、遮罩拦截和移动端适配;关键在禁用背景滚动(body overflow:hidden)、锁定焦点循环、安全高亮、语义化标记及选中后焦点回归触发源。

直接用原生 HTML + JavaScript 实现城市选择弹窗,不依赖 UI 框架是可行的,但必须自己处理焦点管理、键盘导航、遮罩层拦截和移动端适配——否则用户按 Tab 会跳出弹窗,或在手机上点不到选项。
怎么让弹窗真正模态(阻止背景交互)
仅靠 z-index 和 position: fixed 不够。关键是要禁用背景滚动并锁定焦点:
- 弹出时给
加style="overflow: hidden;",关闭时恢复;否则 iOS Safari 会继续滚动页面 - 用
document.querySelector('[role="dialog"]').focus()把初始焦点设到弹窗容器,再监听keydown拦截Tab键,在弹窗内循环焦点(只在可聚焦元素间跳转:如input、button、带tabindex="0"的li) - 点击遮罩层(
div.overlay)时,只关闭弹窗,不触发其下元素的click
城市列表怎么支持搜索 + 分组 + 高亮
纯前端渲染城市数据时,搜索响应速度取决于实现方式,不是加个 filter() 就完事:
- 把城市数据预处理成两级结构:
{ "华北": ["北京", "天津"], "华东": ["上海", "南京"] },避免每次搜索都重排分组 - 搜索时对「城市名」和「拼音首字母」都匹配(例如输入
b要命中“北京”和“包头”),用String.prototype.localeCompare('b', 'zh', { sensitivity: 'base' })或预存拼音字段更稳 - 高亮关键词别用
innerHTML += '<span class="hl">' + keyword + '</span>'—— 会引发 XSS;改用textContent拆分再拼 DOM 节点,或用document.createTextNode()安全插入
为什么 select 元素不适合做城市选择弹窗
原生 <select></select> 在不同系统/浏览器中行为差异极大,尤其在移动端:
- iOS Safari 的
<select></select>是全屏原生控件,无法自定义样式、无法嵌入搜索框、无法显示分组标题 - Android Chrome 对
size属性支持不稳定,设size="10"可能只显示 3 行 - 无法响应
mouseenter或自定义滚动条样式,也难以实现「点击城市后自动关闭+回填到触发按钮」这类交互 - 真要轻量,用
<div role="listbox"> + <code><div role="option"> 语义化标记,配合 <code>aria-activedescendant支持屏幕阅读器最易被忽略的是键盘操作细节:用户按
Enter或Space选中城市后,焦点必须回到触发按钮(比如「所在城市」文本框),而不是留在弹窗里——否则连续操作会断链。这个逻辑常被写死在关闭函数里,但没考虑弹窗从不同入口打开(按钮、图标、输入框右侧箭头)的情况。











