这篇文章里我整理了 8 个很强、却依然被大量低估的浏览器能力。它们不算花哨但真的很实用。有些功能甚至会直接改变你对“前端到底该怎么做”的理解。所以别急着装依赖。先往下看。也许你会发现自己这些年其实绕了不少远路。1. 输入框聚焦了父元素也该有反应给输入框本身加聚焦样式这谁都会。难的是当你想让父容器也跟着变样时事情就开始变得不对劲了。你会下意识想“是不是得加事件监听” “focus 的时候加个 classblur 的时候再删掉” “嗯……好像也不是很复杂”结果写着写着几十行 JavaScript 就冒出来了。 你只是想高亮一个容器却突然开始怀疑自己到底在干嘛。其实浏览器早就给了答案:focus-within它干的事情非常直白只要一个容器里的任意子元素获得焦点这个容器本身就可以被选中并应用样式。.form-field { border: 1px solid #ccc; padding: 12px; } .form-field:focus-within { border-color: hotpink; }div classform-field input placeholderType something meaningful... / /div这类场景最常见的地方就是表单 UI。你原本只是想让整块输入区域在用户操作时更明显一些结果以前得靠 JS 才能完成现在用一条 CSS 规则就够了。而且支持情况也很稳主流浏览器基本都没什么问题。2. 用户断网了不一定要整一套大工程只要你做过 PWA或者稍微碰过需要离线体验的 Web 应用就一定想过这个问题用户突然没网了页面该怎么办可能他在地铁里。 可能他刚进电梯。 也可能只是家里的 Wi-Fi 又发疯了。很多人一碰到这个问题就容易往复杂方向想 先搭一个离线缓存系统再做一个请求重试层再补一个本地队列还得考虑失败回放和数据同步……这些当然都可能有价值。 但在你把整套大工程搬出来之前其实应该先问一句浏览器最基础的能力你用了吗浏览器原生就提供了两个事件online和offline你可以在网络状态变化时立刻感知到然后做出对应处理——比如提示用户、暂停请求、把操作临时存本地或者等网络恢复后再同步。window.addEventListener(offline, () { alert(You are offline. Your internet is gone); }); window.addEventListener(online, () { alert(Youre back. welcome back); });这东西特别适合做“先别慌”的第一层体验处理。当然确实有个现实问题要提醒 浏览器判断“在线”并不等于你的后端服务一定可用。它只能说明设备看起来是联网的。但即便如此这两个事件依然非常值得用。因为在很多场景下用户需要的并不是一套巨型架构而是一个及时、明确、足够友好的反馈。3. 别现在跑等浏览器有空再跑第一次看到这个 API 的时候我也觉得它有点鸡肋。它的意思差不多就是“浏览器空闲的时候再帮我执行这段代码。”我当时的第一反应是“这有啥意义代码不就是该执行就执行吗”后来慢慢才意识到不是所有任务都必须马上做。有些事情根本不急。 比如埋点统计、预加载、不影响首屏的数据处理或者某些完全可以放在后台慢慢跑的逻辑。说白了页面正在渲染、组件正在更新、用户正在交互的时候你真的不该让这些低优先级任务来抢主线程。这就是requestIdleCallback最适合发挥作用的地方。它会把时机选择交给浏览器 什么时候空一点什么时候不影响体验再执行这段代码。function showMessage() { console.log( This ran when the browser was idle!); } if (requestIdleCallback in window) { requestIdleCallback(showMessage); } else { setTimeout(showMessage, 0); }这类能力特别适合处理分析和埋点后台数据整理可以延迟的非关键任务不该阻塞主线程的琐碎工作一旦你开始用“高优先级 / 低优先级”的方式看待前端任务很多原本混在一起的逻辑都会变得清楚。顺便提醒一句Safari 目前还不完整支持所以最好保留一个 fallback。4. 动画的节奏应该让浏览器来掌控很多人第一次写动画时都会先想到setInterval。比如让一个盒子往右移动setInterval(() { box.style.left box.offsetLeft 5 px; }, 16);它确实能动。 而且刚开始看好像也没什么问题。可只要时间一长你就会慢慢发现不够顺。 有时会卡。 节奏不稳。 在某些设备上尤其明显。问题在于setInterval根本不关心浏览器什么时候真正重绘。它只是粗暴地按你设定的时间间隔去“猜”——大概每 16ms 跑一次。但浏览器的刷新节奏不是这么工作的。更合适的方式是requestAnimationFrame。function moveBox() { box.style.left box.offsetLeft 5 px; requestAnimationFrame(moveBox); } requestAnimationFrame(moveBox);这两种写法的差别表面看不大实质上却很关键。前者是在说“你每隔 16ms 跑一次吧差不多就行。”后者是在说“你准备重绘的时候再帮我执行。”而动画这种事真正需要的恰恰就是“跟着浏览器节奏走”。一旦把时机交回给浏览器流畅度通常就会明显改善。5. 让组件根据自己而不是根据屏幕决定怎么变这些年一提响应式大家第一反应基本都是媒体查询。它当然好用而且过去也确实解决了大量问题。可问题是媒体查询看的始终是整个视口。 而现实中的组件很多时候根本不是铺满全屏的。比如一个卡片放在大屏里的窄侧边栏里 又或者它被塞进一个宽页面中的小区域里。这时候屏幕明明很宽可组件自己其实很挤。 你却还在按照 viewport 的尺寸做判断。于是布局开始出问题。 然后你就开始补 hack。 再然后你开始怀疑是不是自己响应式没写对。其实你真正想表达的往往不是“如果屏幕足够宽……”而是“如果这个组件所在的容器足够宽……”这就是Container Queries出现的意义。它让组件可以根据自身容器的尺寸来响应而不是永远盯着整个屏幕。.card-wrapper { container-type: inline-size; } .card { display: grid; gap: 10px; } container (min-width: 400px) { .card { grid-template-columns: 1fr 2fr; } }这样一来同一张卡片放在不同地方就可以自然长出不同布局在窄侧边栏里它自动堆叠在宽内容区里它自动并排不用额外判断不用写一堆例外情况也不用再问“为什么 iPad 上偏偏这里炸了”这类能力真正提升的不是炫技程度而是组件独立性。 你终于可以把组件写得更像组件而不是“绑定在页面结构上的半成品”。6. 别再拿 Math.random() 当唯一 ID 生成器了很多人都写过类似这种代码const uniqueId Math.random().toString(36).slice(2);看起来没毛病。够短。 够方便。 够像随机。 而且跑起来也确实能用。于是它就这么被你心安理得地放进项目里了。可问题在于Math.random()从来就不是为“生成可靠唯一 ID”设计的。它的问题并不总会立刻暴露但隐患一直都在它不是真正强随机实现依赖浏览器引擎可能出现模式性时间久了碰撞概率会越来越难忽视换句话说它属于那种“刚开始看起来够用了等真出事时你才知道不够。”浏览器其实早就给了更靠谱的方案crypto.getRandomValues()const bytes new Uint8Array(8); crypto.getRandomValues(bytes); const uniqueId Array.from(bytes) .map(b b.toString(16).padStart(2, 0)) .join();这类方式更合理的原因在于随机性更强熵更高模式更少冲突概率更低本质上你是在把“随机”这件事交给浏览器更专业的能力来处理而不是继续拿一个本就不为此而生的函数硬顶。尤其是当 ID 不只是前端临时玩玩而是真正参与状态、节点映射、缓存或数据记录时这种区别很容易变成线上 bug 和稳定系统之间的分界线。7. 别一做弹窗就先装 Modal 库几乎每个项目都会走到这一步“我们要做个弹窗。”然后事情就会迅速开始失控。装库。 调 z-index。 补焦点管理。 修滚动穿透。 再顺手处理 ESC、点击遮罩关闭、无障碍、层级冲突……最后你会发现自己为了一个弹窗忙得像在造操作系统。但现在浏览器其实已经很明确地告诉你这件事我能自己来。它给出的答案就是dialog一个原生的对话框元素。button idopenBtnDelete Account/button dialog idmodal h3⚠️ Delete Account/h3 pThis action cannot be undone. Are you sure?/p div classactions button idcancelBtnCancel/button button idconfirmBtnYes, Delete/button /div /dialog为什么它值得认真看待因为很多你过去需要自己补的事它已经原生处理了自带对话框行为焦点管理更自然可访问性更友好背景区域会失活ESC 可关闭不必靠一堆 CSS 和 JS 小心翼翼拼出来当然复杂弹层系统并不是从此都不需要第三方方案了。 但至少对于“我们只是需要一个正常弹窗”这种场景很多项目真的没必要一上来就把依赖装满。有时候你以为自己缺的是一个库 其实你缺的只是多看一眼平台本身。8. 浏览器其实已经会“听你说话”了很多人一想到语音输入脑子里立刻冒出来的都是重型方案AI 模型。 语音服务。 第三方 SDK。 一堆复杂配置。 甚至默认觉得“这东西肯定很重”。但现实有时候比想象简单得多。浏览器本身其实已经提供了语音识别能力。 也就是Speech Recognition API。const SpeechRecognition window.SpeechRecognition || window.webkitSpeechRecognition; const btn document.getElementById(startBtn); const box document.getElementById(box); if (SpeechRecognition) { const recognition new SpeechRecognition(); btn.onclick () { recognition.start(); }; recognition.onresult (e) { const text e.results[0][0].transcript.toLowerCase(); box.textContent text; box.style.background text; }; } else { btn.textContent Not supported ; }这里发生的事情其实很直接你点击开始浏览器开始监听用户开口说话浏览器把语音转成文字结果立刻交给你使用没有自己训练模型。 没有复杂接入流程。 也不一定非得拉一堆大库进来。这并不意味着它适合所有语音场景。 但至少说明了一件事有些你以为必须靠“更大方案”解决的问题浏览器早就给出了足够好用的第一层能力。最后精通 React 面试从零到中高级(针对面试回答)CSS终极指南Vue 设计模式实战指南20个前端开发者必备的响应式布局深入React:从基础到最佳实践完整攻略python 技巧精讲React Hook 深入浅出CSS技巧与案例详解vue2与vue3技巧合集全栈AI·探索涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏案例驱动实战学习点击二维码了解更多详情。