
大家好!😁。
Code Review 的時候,我最怕看到什麼?
不是複雜的算法,也不是什么正則。而是明明一個 HTML 標籤就能搞定的事,有人非要寫幾百行 JS + CSS 去重新發明輪子。
前幾天,我看到一個新同學為了寫一個摺疊面板(Accordion),引入了一個重型的第三方庫,還寫了一堆 useState、onClick 和動畫邏輯。
我默默地把他的代碼全刪了,換成了 3 行 <details>。他看我的眼神,仿佛在看一個外星人🤣。
在 2025 年的今天,瀏覽器原生 HTML 的能力早已今非昔比。很多我們習慣用 JS 去模擬的互動,現在不僅有原生支持,而且性能更好、兼容性更強、無障礙(a11y)更完善。
今天,我就來盤點 5 個被嚴重低估的 HTML 標籤👇。
<details> & <summary>:摺疊元件你是不是還在寫這樣的 React 代碼?
// JS 模擬版
const [isOpen, setIsOpen] = useState(false);
return (
<div className="accordion">
<div className="header" onClick={() => setIsOpen(!isOpen)}>
點擊展開 {isOpen ? '⬆️' : '⬇️'}
</div>
{isOpen && <div className="content">...</div>}
</div>
);
為了這個功能,你還得寫 CSS 動畫,還得處理鍵盤事件(Tab 鍵能不能選到?回車能不能展開?等等)。
HTML 原生寫法:
<details>
<summary>點擊展開</summary>
<div class="content">
這裡是展開後的內容,原生支持 Ctrl+F 頁內搜尋!
</div>
</details>
display: none),瀏覽器的 Ctrl+F 往往搜不到。但 <details> 裡的內容,即使摺疊,瀏覽器也能搜到並自動展開!配合 CSS 👇
details {
border: 1px solid #ccc;
border-radius: 6px;
padding: 8px;
}
summary {
cursor: pointer;
font-weight: bold;
}
/* 包住內容,讓它能動畫高度 */
details > .content {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height .45s ease, opacity .3s ease;
}
/* details 處於 open 狀態時 */
details[open] > .content {
max-height: 200px; /* 你內容高度大概多少設多少,足夠大即可 */
opacity: 1;
}
依然可以做動畫。
<dialog>:彈窗元件寫模態框(Modal)是前端最大的坑之一。你需要考慮:
z-index 層級會不會被遮擋?Esc 鍵關閉?為了解決這些,我們通常會引入 Antd Modal 或者 React Portal。但在輕量級場景下,原生 <dialog> 才是神🫅。
HTML 原生:
<dialog id="myModal">
<form method="dialog">
<p>這是一個原生模態框</p>
<button>關閉(自動)</button>
</form>
</dialog>
<button onclick="myModal.showModal()">打開彈窗⏏</button>
z-index 和 overflow: hidden。/* 背景遮罩 */
dialog::backdrop {
background: rgba(0, 0, 0, 0.45);
backdrop-filter: blur(3px);
transition: opacity .3s ease;
}
Esc 關閉,自帶焦點管理,表單提交自動關閉。<datalist>:搜尋自動補全當產品經理要求做一個帶搜尋建議的輸入框時,你的第一反應是不是:“快!引入 Select2 或者 Antd AutoComplete!😖
且慢。如果只是簡單的建議列表,幾 KB 的 JS 庫都顯得太重了。
HTML 原生版:
<label>選擇你喜歡的框架:</label>
<input list="frameworks" />
<datalist id="frameworks">
<option value="React">
<option value="Vue">
<option value="Svelte">
<option value="Angular">
<option value="Solid">
</datalist>
<fieldset> & disabled:一鍵禁用整個表單場景:用戶點擊提交按鈕後,為了防止重複提交,我們需要禁用表單裡的所有輸入框。
JS 笨辦法:
// 還要一個個去拿 DOM,或者維護一個 loading 狀態傳給所有組件
inputs.forEach(input => input.disabled = true);
buttons.forEach(btn => btn.disabled = true);
HTML 原生寫法:
<form>
<fieldset disabled id="login-group">
<legend>登入</legend>
<input type="text" placeholder="用戶名">
<input type="password" placeholder="密碼">
<button>提交</button>
</fieldset>
</form>
<script>
// 一行代碼搞定狀態切換
document.getElementById('login-group').disabled = true;
</script>
這是一個極好的分組思維。透過給 <fieldset> 設置 disabled,瀏覽器會自動禁用內部所有的 <input>, <select>, <button>。不用寫循環,不用維護複雜的 State。
<input type="file" capture>:H5 調用手機相機場景:業務需要用戶上傳一張照片,可以是相簿選的,也可以是當場拍的。
很多新手的反應是:是不是要接微信 JSSDK?是不是要寫個 Bridge 調原生 App 能力?
答案是不需要!
HTML 原生:
<input type="file" capture="environment" accept="image/*">
只要加上 capture 屬性,在移動端(iOS/Android)點擊上傳時,系統會直接拉起相機,而不是讓你去選文件。拍完照後,你拿到的就是一個標準的 File 對象。
不需要什麼 JS SDK,實現原生級體驗👍。
我總是強調 最好的代碼,是沒有代碼!
HTML 標準一直在進化,很多曾經需要重型 JS 才能實現的功能,現在已經成了瀏覽器的出廠設置了。
使用這些原生標籤,不僅能減少打包體積,更能讓你的應用在可訪問性和性能上天然領先。
下次再想 npm install 一個 UI 庫之前,先查查 MDN。說不定,HTML 早就幫你做好了🤔。