1. 快速认识 Emmet:为何它是我最推荐的编码效率工具
如果你每天都要写 HTML,却还在一个字母一个字母地敲标签,那你和那些用 Emmet 一分钟生成整页结构的人,差的不是手速,而是一套缩写语法。我记得第一次看同事敲div.box>ul.list>li.item*5然后按下 Tab,整段嵌套结构瞬间展开,当时我整个人是愣住的。那是很多年前在 Sublime Text 时代,插件还叫 Zen Coding,后来升级为 Emmet,如今已经被 VSCode、WebStorm、Sublime Text 这些主流编辑器直接内置,几乎不需要额外配置就能用。
Emmet 解决的核心痛点只有一个:把写 HTML/CSS 时的重复机械输入量降到最低。它的思路是,用一段类似 CSS 选择器的缩写表达出你想要的 DOM 结构,再用 Tab 或快捷键展开成完整代码。这个思路之所以这么多年不过时,是因为它没有锁死任何框架或 UI 库,生成的永远是干净的标准标签代码,不管你是写原生 HTML、Vue 模板、React 的 JSX,还是小程序结构,它都能直接帮上忙。
这篇文章适合谁?如果你刚入门前端,尽早养成用 Emmet 的习惯,能把宝贵时间花在结构和逻辑思考上,而不是浪费在闭合标签上;如果你已经写了几年代码,但一直只会在编辑器里手动复制粘贴前面的片段,那这里面有很多进阶写法可以帮你提速;就算你是偶尔改改页面的后端同学,掌握几个核心缩写也足以节省大量时间。下面我会从环境、语法、实战、踩坑四个维度完整讲一遍。
1.1 它到底好在哪:从手速竞争切换到语法表达
写前端页面最消耗耐心的地方,其实不是 CSS 布局,也不是交互逻辑,而是大段的重复结构。典型如后台管理系统,一页往往就是几十个表格行或者表单字段,这些结构长得相似,却又有细微差异,复制粘贴很容易出错,手动敲又非常低效。
Emmet 的出现让这件事变成了“写选择器”。比如你想生成一个三列表头配五行数据行的表格,用手敲大概需要三十多次回车和标签闭合,用 Emmet 只需要一行:table.table>thead>tr>th*3^^tbody>tr*3>td*3,然后 Tab 展开。它把“结构关系”压缩成了>(嵌套)、+(并列)、^(向上退层)这几个符号,让设计到实现的翻译过程变得很直接。
另外别小看了$这个序号占位符。批量生成带有递增 id 或 class 的列表时,它就像一个简单版模板引擎。举个例子,li.item$@3*4展开后是 item3、item4、item5、item6,你完全不用手动改数字。这种小幅度的自动化,在实际业务里攒起来,效率提升非常可观。
1.2 编辑器生态:你的工具够用吗
Emmet 在主流编辑器中的支持情况,简单说就是“默认内置”和“插件安装”两大类,绝大多数场景不需要折腾:
- VSCode:直接内置,默认通过 Tab 键展开,设置项里搜
emmet就能看到完整配置; - WebStorm 全家桶:内置,且对 Emmet 的支持非常深,连文件类型扩展都考虑到了;
- Sublime Text 3/4:需要安装 Emmet 插件,属于老牌标配;
- Neovim/Vim:有第三方的 emmet-vim 插件,设置稍多但体验完整;
- 在线编辑器 CodePen、JSFiddle 以及一些低代码平台的 HTML 编辑区,很多也集成了 Emmet 展开能力。
提示:如果你用的是 VSCode,打开设置搜索
emmet.includeLanguages,把vue、javascriptreact这些文件类型关联进去,才能在对应文件里使用 HTML 缩写。这个坑很多人一开始都会踩。
2. 从一条缩写到完整 HTML:Emmet 核心语法骨架
我始终觉得,Emmet 的语法并没有多少需要死记硬背的地方,它本质上就是“CSS 选择器 + 几个符号”。只要你会写.class和#id,剩下的就是在记忆几个符号的语义。
2.1 最常用符号一览:> + ^ * () {} $
>表示子级,例如nav>ul>li,生成三层嵌套;+表示同级,例如div+p,生成并列的两个标签;^表示向上退一级,例如div>p^span,span会回到div的父级,与div并列;*表示重复,例如li*5生成五个li;()表示分组,可以让一部分结构先算好再展开,例如(header>nav)+main;{}表示插入文本内容,例如p{你好};$表示自动递增序号,配合*使用效果最佳。
这里面最容易被忽略的是^的用法。很多人写多层嵌套时,想要跳回上一层,只会不断加括号或者拆开写,其实用一个^就能解决。比如div.wrap>header>nav^^footer,展开后footer不在header里,而是和header并列,属于div.wrap的子级。如果需要跳两层,就写两个^。虽然实际项目里跳两层的情况不算多,但理解这个规则会帮你轻松读懂一切复杂缩写。
2.2 默认值和隐式标签:少打字的关键
Emmet 有一条很贴心的规则叫做“隐式标签”。当你省略标签名时,它会根据父级自动推断:
- 单独的
.box会展开成<div class="box"></div>,因为默认元素是div; - 在
ul或ol下直接写.item,会展开成<li class="item"></li>; - 在
table>下写.row,会推断为<tr class="row"></tr>; - 在
select>下写.opt,会推断为<option class="opt"></option>。
这个规则非常实用。最典型的场景就是列表生成:ul.breadcrumb>.item*3比ul.breadcrumb>li.item*3少敲几个字符,并且语义同样清晰。刚开始你可能不太敢用,因为不确定展开结果,但实际上你只需要记住一个检查方法:缩写的最后一级如果只有一个.或#开头的选择器,Emmet 会按上下文自动补标签;如果上下文无法推断,一律用div。
2.3 属性、文本和自闭合标签的处理
除了 id 和类名,Emmet 还能直接在缩写里写属性。中括号[属性="值"]就是干这个用的。比如a[href="https://example.com"][target="_blank"]会生成一个带完整属性的链接。在实际项目中,最常用的其实是配合输入框生成表单:input[type="email"][name="user_email"][placeholder="邮箱"]。
文本内容用花括号{}包裹,比如button.primary{确认提交}展开成<button class="primary">确认提交</button>。需要注意 Emmet 的文本不会做特殊转义,如果你要放入很多中文字符,并且里面还混合了特殊符号,建议展开后再检查一遍。至于自闭合标签,input、img、br等标签 Emmet 会按 HTML 规范正确生成,一般不需要手动处理。
实操心得:很多人喜欢在缩写里写很长的中文默认内容,比如
p{这里是一段默认文案}。我一般不建议这样做,因为展开之后你还得手动删。更好的做法是先只建结构,把文本占位符留空,或者用lorem系列占位文本。p*3>lorem一行就能生成三段标准的英文占位段落,真正改文案时反而省心。
3. HTML 高频写法实战:直接抄作业
语法说到底只是工具,真正有价值的是你在业务里怎么用它。这一节我会直接放一些我平时在项目中反复使用的写法,每一段都对应一个典型场景,你可以直接复制到编辑器里体验。
3.1 页面骨架、导航和板块结构
新建一个 HTML 文件,输入!再按 Tab,能直接生成 HTML5 文档骨架,包括<!DOCTYPE html>、html、head里的meta、title以及body。以前还需要手动补字符集和视口配置,现在一行搞定。
拿到骨架之后,最常见的需求是快速搭出页面的整体区块:
header.site-header>nav.navbar>.nav-item*4>a[href="#"]这一行展开后就是一个带四个链接的顶部导航结构。再配合main>section*3把主体区域分成三块,一个静态页面的基本框架就在两三次 Tab 之间成型了。很多人觉得 Emmet 只适合做小 demo,实际上后台系统那种几十个模块的页面,反而更需要这种快速生成结构的方式。
3.2 表单页面的批量生成
表单页面是我个人觉得 Emmet 性价比最高的应用场景之一。你想想看,注册页里通常有用户名、邮箱、密码、确认密码、协议勾选、提交按钮,如果是手打,每块内容都是四五行代码的复制粘贴,复制完还得改 name、id、placeholder。用 Emmet 可以写成一整段:
form.reg-form>(.form-group>label{用户名}+input[name="username"])+(.form-group>label{密码}+input[name="password"][type="password"])+button.btn[type="submit"]{注册}这样展开后,每个表单项都有清晰的 class 分层,属性也是你预先设计好的,省掉的不只是打字时间,更是来回检查属性是否抄错的时间。我在实际项目里还会搭配 Vue 或 React 的受控组件写法,把v-model="form.username"或value={form.username} onChange={handleChange}写进属性里,展开即模板,基本不用二次修改。
3.3 表格与复杂结构的整体生成
表格是 Emmet 分组语法最好的演示场景,因为它天然就是嵌套加重复的组合。老式后台管理系统最常遇到的就是表格页面:
table.table.table-hover>thead>tr>th*4^^tbody>tr*3>td*4注意这里^^的作用是让tbody回到table层级,而thead>tr>th*4这个分组不需要括号也能正确展开,因为^^已经把层级退回来了。如果你希望多个td里放不同的按钮,还可以在td内部继续嵌套:
table>tbody>tr*2>td*3>button.btn{操作}看到没,td*3>button.btn{操作}会给三列都生成操作按钮。这种批量生成能力,配合多光标编辑,几乎可以覆盖所有表格类需求。
3.4 结合 JSX 和 Vue 模板的使用技巧
如果你在 React 或 Vue 项目里写 JSX,要注意 Emmet 的展开目标和原生 HTML 不太一样。JSX 中你更多是生成组件结构,比如div.card>h2.title+p.desc这样。但有一个大坑:Emmet 生成的是标准 HTML 属性,比如class,而在 JSX 里需要写成className,所以展开后经常要把class统一改成className。
在 Vue 模板中,template里可以使用标准 HTML Emmet,而<script setup>部分则不行,因为那是 JS 上下文。VSCode 里需要在设置中把javascriptreact或typescriptreact加入emmet.includeLanguages,否则在.jsx文件里按 Tab 没有反应。
实操心得:在 Vue 中,我习惯把组件名作为 Emmet 的标签名记忆,例如
el-form、el-input、a-button这种,它们在 Emmet 中会被当作合法标签并正确展开成<el-form></el-form>。不过要注意的是,展开后如果标签内容为空,部分格式化插件可能给你强行补成自闭合,建议展开后立刻让格式化插件处理一次。
4. CSS 缩写实战:样式代码也能快到离谱
大多数人学 Emmet 会先学 HTML 部分,然后发现一个惊人的事实:CSS 的缩写才是真正让你“上瘾”的功能。因为在 CSS 里,一行属性就够了,Emmet 对属性名的缩写力度却非常大,输入几个字母直接出整条属性。
4.1 CSS 缩写的底层逻辑,真的不用死记
Emmet 的 CSS 缩写不是瞎编的,它基本遵循“属性名的首字母 + 关键部分”这个规律。比如margin记成m,padding记成p,width记成w,height记成h,font-size记成fz,line-height记成lh,background-color记成bgc。这些缩写本身就像拼音或英文单词的缩写,看到基本能猜个七八成。
但有几点得提醒你:不要随意在缩写字后面乱加东西。比如你想写margin: 20px auto;的话,可以直接输入m20auto,但为了可读性更推荐m0-auto,它会把连字符作为两个值之间的分隔点。值里面不能带空格,因为空格是编辑器常见的触发方式,在缩写里你需要用-或直接连接来表示多值。
4.2 高频 CSS 缩写速查表
我把日常项目里出现频率最高的缩写整理成一份表,方便你贴在手边查:
| 缩写 | 展开结果 |
|---|---|
w100 | width: 100px; |
w100p | width: 100%; |
h10 | height: 10px; |
m0 | margin: 0; |
m0-auto | margin: 0 auto; |
p10-20 | padding: 10px 20px; |
mt20 | margin-top: 20px; |
p10 | padding: 10px; |
fz14 | font-size: 14px; |
fw700 | font-weight: 700; |
lh1.5 | line-height: 1.5; |
df | display: flex; |
dib | display: inline-block; |
posa | position: absolute; |
posr | position: relative; |
bd | border: ;(继续补值) |
bdr | border-right: ; |
bdrs4 | border-radius: 4px; |
bgc#fff | background-color: #fff; |
bxsh | box-shadow: ; |
注意,上表里的bdr是border-right,如果要写圆角,习惯上是bdrs。这俩容易混淆,我第一次用时也翻过车,后来在 VSCode 里给自己配了自定义补全才彻底解决。
4.3 单位、颜色和响应式关键的几个用法
值带单位的处理方式是 Emmet 设计得比较巧妙的一部分。默认情况下w100生成的是像素单位100px,这是 Web 开发最常用的单位。而当你需要百分比时,用p结尾,比如w50p生成width: 50%;;用e结尾是em,用r结尾是rem,比如fz1.5r生成font-size: 1.5rem;。
颜色值可以直接跟在缩写后面,c#333会生成color: #333;,bgc#f5f5f5会生成background-color: #f5f5f5;。这里有个小坑:如果颜色值作为第三个或第四个值出现,比如box-shadow: 0 0 4px rgba(0,0,0,0.3)这种带括号和逗号的长值,Emmet 的解析可能不符合预期,我更推荐先展开一个空属性,再用编辑器的颜色选择器去补色值。
至于!important,Emmet 是在缩写的最后写!再展开,比如m0!得到margin: 0 !important;。这个很实用,但也容易产生滥用风险。我的原则是:只有在覆盖第三方 UI 库强制样式时才用,自己的代码里尽量避免,否则后面维护时排查优先级会非常痛苦。
4.4 渐变、动画、flex 等复杂属性的写法
很多进阶教程会讲到一些复杂属性的快捷写法,但实际用到频率并没有想象中高。我的经验是:Emmet 对 flex 布局相关的支持足够好用,比如df生成display: flex;,jcc生成justify-content: center;,aic生成align-items: center;。这几个配合起来,几乎是现代前端布局的日常主力,非常值得背下来。
渐变和动画这种属性,缩写虽然也能生成骨架,但值非常长,还不如先写background:然后用编辑器自带的颜色选择器去点。因此我不会硬记bg后面那一大串,建议大家把 Emmet 用在“结构搭建”和“高频属性”上,真正复杂的长属性交给代码补全和颜色选择器,这才是效率最大化。
5. 进阶场景:让 Emmet 融入真实工作流
学会缩写的语法之后,很多人的提升瓶颈就不是“会不会”而是“用不用”了。这一节我会讲讲怎么把 Emmet 揉进日常开发流程里,让它成为你肌肉记忆的一部分。
5.1 多光标与 Emmet 的组合技
最经典的一套操作是先展开结构,再用多光标修改。比如你写了一个ul>li.poster*6>img[src=""],展开后六个img的src都为空。此时我把光标放到第一个src,按Ctrl+D逐个选中,或者直接用Ctrl+Shift+L全选,然后统一粘贴图片地址,效率比复制粘贴整个标签高得多。
Emmet 本身并不负责批处理流转数据,你还需要配合alt+shift+↑/↓复制行、Ctrl+D多选这些操作。我自己做列表页时,通常是:先用 Emmet 生成骨架,再用多光标统一填充属性和文本,一条 20 行的列表在 30 秒内完成,非常舒服。
5.2 写在编辑器外:从设计图到 HTML 的翻译思维
有些同学用 Emmet 很流畅,但拿到设计图还是发呆,因为不知道先写什么。这里我分享一个个人习惯:拿到设计稿先做“区块拆解”,把页面分解成“头部 / 主体 / 底部”这种大块,再继续拆每一块里的小结构。拆完之后,把每个区块翻译成一行 Emmet 缩写,依次展开,再填充样式和内容。
比如看到一个卡片,先在心里把它拆成“外层容器、图片、标题、正文、按钮”,对应 Emmet 就是figure.card>img+figcaption>h3+p+button。你并不需要背任何模板,只需要掌握拆解的方式。这种思维方式比 Emmet 本身更值钱,值得在日常开发中刻意训练。
5.3 自定义 snippet:把固定业务模板塞进 Emmet
除了 Emmet 自带的语法,它还支持自定义 snippets,可以配置在全局的snippets.json中。你完全可以把自己项目里高频出现的结构定义成一行缩写,比如:
"mobilePage": "<div class=\"page\">\n <header class=\"page-header\">...</header>\n <div class=\"page-content\">...</div>\n <footer class=\"page-footer\">...</footer>\n</div>"这样每次新建页面,只要输入mobilePage再展开,就能得到完整的页面骨架。我前几年的项目里甚至把公司的组件模板全部定义成了 snippets,从 30 多行重复代码变成一行展开,收益极其可观。自定义 snippets 的优先级要高于默认语法,配置时注意不要和现有缩写冲突。
5.4 Emmet 和代码片段工具怎么分工
写代码时很多人会把 Emmet 和编辑器自带的代码片段(snippets)搞混。简单区分:Emmet 适合“动态生成有规律的结构”,比如列表、表格、表单,因为它的核心是表达式;snippet 适合“固定的模板片段”,比如一个组件的完整骨架,因为文本不会经常变化。
如果一个项目里大量使用固定模板,比如公司后端管理系统的“列表页”和“表单页”,我就用 snippets;如果是每次数据不同的具体列表,就用 Emmet。两者结合,而不是二选一,才是我实际工作流中最舒服的状态。
6. 常见问题与踩坑实录
照例,分享几个我自己用 Emmet 时常遇到的问题和一些排查思路。这些问题看起来都很小,但第一次遇到时确实会卡住。
6.1 按 Tab 没反应?先检查这几个地方
最常见的原因是光标所在位置的问题。Emmet 展开必须把缩写放在一个可以被解析的位置,比如行首、空格后或者标签之间的某个位置。如果你把缩写紧跟在一段中文后面,有些编辑器就无法识别。还有一个原因是 VSCode 中虽然默认启用了 Emmet,但文件类型没被识别,也会静默失败。
处理方法很简单,分三步排查:
- 确认文件类型是 HTML 或已关联的类型,看右下角状态栏;
- 把光标放在缩写末尾,确认前后有空格,或者干脆新起一行单独输入缩写;
- 在命令面板输入
Emmet: Expand abbreviation,看是否有展开结果。
如果这三点都正常,大概率是快捷键冲突,需要去设置里手动把 Tab 键命令改成emmet.expandAbbreviation。
6.2 展开结果和预期不一致的 3 个常见原因
第一个原因是编号$的起始位置理解错了。$默认从 1 开始,如果你想要从 3 开始,得写$@3。想要倒序可以写$@-。我第一次用的时候以为$@2表示从 2 开始,结果发现不是,花了半分钟才反应过来。
第二个原因是隐式标签的推断。table>tr展开时,tr是明确标签没问题,但当你写table>.row*3>td*2时,.row会默认被推断为tr,这个行为是符合 HTML 语义的。不过在dl(定义列表)这种不那么常见的标签下,推断结果可能不符合预期,所以复杂的结构我还是建议把标签名写完整。
第三个原因是括号导致的歧义。(div>p)+span和div>p+span语义完全不同,后者会把span放在p的兄弟位置,前者则把span放在与整个div并列的位置。如果你展开后层级不对,先回看自己是否少写了括号。
6.3 值得养成的几个书写习惯
最后说几个我踩过坑后总结出来的习惯。
第一,缩写尽量保持一行可读。超过三四个层级还憋在一行里,自己回头看都费劲,更别提和其他人协作。此时适合用分组,或者干脆拆成两三段逐步生成。
第二,展开后立即格式化。很多格式化插件会自动把标签拆行,让结构更清晰。我自己的习惯是生成后立刻执行一次格式化,避免后面代码混乱。
第三,用 Emmet 但不被 Emmet 绑架。有些同事为了保证“一行生成整个页面”,会在缩写里塞大量属性和文本,结果展开出几百行的压缩代码,格式化后还得大量手动修改。我的原则是,结构越复杂,缩写越短,把细节留给多光标和后续编辑完成。
对我个人来说,Emmet 是极少数学了十年还在天天用的“小而美”工具。它不需要你花很长时间背诵,却能持续地减少日常工作的重复感。如果你现在还处于手动敲标签的阶段,真的建议从今天的下一个页面开始,试着写第一段缩写。你可能会发现,原来写结构也有一种接近“打字如飞”的快乐。