
1. 为什么要掌握Emmet我亲历的一次惨痛对比先讲一个我真实的开发片段。前几年在公司写一个后台管理系统的列表页数据表格要渲染七八列每列还有筛选条件、排序按钮、操作按钮外加表头工具栏和批量操作区。我当时的习惯是一个标签一个标签手敲、、一行一行复制粘贴再改内容。一个完整的列表模板写下来加上弹窗表单部分大概消耗了我四十多分钟。后来有位前辈路过看了一眼屏幕二话不说在我编辑器里敲了一行.toolbar.btn-grouptable.tabletheadtrth*6tbodytr*10td*6然后按下快捷键几十行HTML瞬间铺满屏幕。那个场面我到现在都记得从那一刻起我开始系统学Emmet。今天这篇博客就是想把我在实际项目中用Emmet的经验沉淀下来包含语法、配置、坑点和心得给正在写页面但还没重视效率的朋友一份参考。Emmet是一个文本编辑器插件核心能力是把类似CSS选择器形式的缩写展开成完整的HTML结构同时也在CSS属性书写上有大量缩写支持。它解决的本质问题非常直接手写重复性标签代码耗时且容易出错而Emmet把“描述结构”和“生成代码”分离你只需要按思维习惯写出嵌套关系剩下的机械劳动交给工具。适合谁前端开发、后端偶尔碰页面的同学、写邮件模板的运营、甚至做静态原型的交互设计师只要你的工作里离不开HTML和CSS都值得花半小时读这篇。2. 深度拆解Emmet的核心逻辑为什么一套语法能吃遍HTML和CSS2.1 Emmet的底层设计思路用CSS选择器思维描述HTML学Emmet最关键的一步是转换思维。你已经在写CSS选择器了而Emmet无非是把同样的语法搬到“生成HTML”这个场景。比如你在CSS里写.box表示类名为box的节点在Emmet里输入.box并展开得到的就是div classbox/div。同理#main对应div idmain/divul.nav对应ul classnav/ul。为什么这种映射能成立因为HTML的绝大多数视觉区块都是用div包裹的而类名和ID又是最常用的定位锚点。Emmet默认把不带标签名的缩写视为div这恰好符合实际开发中“class和id比标签名更常变”的真实场景。这个设计的聪明之处在于它不需要你额外学一套符号而是直接复用你已经掌握的CSS知识降低了学习门槛。理解了底层映射很多操作就顺理成章。比如p.content生成p classcontent/pspan.badge-danger生成span classbadge-danger/span规律一目了然。如果你哪天忘了某个标签名的缩写规则只要回头想想CSS选择器的写法基本就能猜出八分。与其说Emmet是一套新工具不如说它重新组织了你已有的知识。2.2 嵌套结构、、^三个符号的组合逻辑实际开发里的HTML几乎都是多层嵌套的Emmet用三个符号表示节点间的结构关系后代关系表示右边的元素是左边元素的子节点兄弟关系表示右边元素和左边元素是并列的^爬升一层从一个嵌套层级跳出到父级再继续写兄弟节点举个例子导航栏最常见的结构nav.navbarul.nav-listli.nav-item*3a[href#]这段缩写的展开过程是先创建nav classnavbar里面放一个ul classnav-list这个ul里有3个li classnav-item每个li里有一个a href#。符号把层级关系串联起来*3重复li节点三次。整个结构从顶层到底层一条线读完视觉上就是一棵树。^符号是很多新手容易忽略但实际很常用的。它的作用是返回到父级。看这个例子div.contentp.first^p.second如果不加^.second会变成p.first的子节点。加了^之后p.second回到div.content级别和p.first成为兄弟关系。我在写页脚、侧边栏和主体内容并列的布局时经常用到这个符号。嵌套符号搭配分组括号使用威力更大。比如div.row(div.colspan{$})*3括号把div.colspan{$}当作一个整体分组后面的*3作用于整个分组生成三组包含span的col结构。这种写法在栅格布局、卡片列表、商品展示区等重复性区块里几乎天天用。2.3 文本内容与数字递增{}和$的配合用法写了标签和结构最终页面里要有文字。Emmet用大括号{}包裹要填入的文本内容文本会自动放到当前节点的内部。你可能觉得这没什么稀奇但配合*和$符号它能直接生成有序的内容序列比如表格、列表、分页按钮。$是Emmet里的数字占位符从1开始递增。最基础的用法ulli.item$*5生成五个li类名分别是item1到item5。文本里的数字也同理p{第$段}*3生成三段话内容分别是“第1段”、“第2段”、“第3段”。多个$连用可以补零比如$$生成01、02、03这在做编号类内容时非常实用。数字的起始值和步长也可以用控制。$3表示从3开始递增$-表示倒序$-3表示从3开始倒序。比如分页器ul.paginationli.page-item*5a[href#]{$-}生成5个分页链接数字从5递减到1。倒序在很多场景下都有奇效比如时间线、倒序编号的文章列表一行缩写就能搞定。2.4 属性简写快速给标签添加属性和值属性写在方括号[]里这是Emmet语法中最直白的一部分。a[href#]生成带href的链接img[src alt]生成带src和alt的图片标签。但这里有个细节值得注意某些高频标签的必需属性已经被Emmet内建默认值覆盖了比如a自带hrefimg自带src和altlink自带relstylesheet href。所以你在编辑器输入a然后Tab展开直接得到的是a href/a不需要额外手写href。这也解释了为什么很多教程都建议你少写属性简写——默认值已经帮你兜底。真正需要用方括号的场景是补充默认值没覆盖的属性比如input[typeemail]、meta[charsetutf-8]、a[href#>bxsh展开结果box-shadow: inset 0 0 0 rgba(0, 0, 0, 0.5);默认给了完整的参数结构你只需要把inset删掉、改数值就行。再比如线性渐变lg()展开为background: linear-gradient();这样你填入角度和色值即可。虽然现在很多场景下我们直接在CSS文件里用变量或Tailwind类去代替手写样式但在写Demo、邮件模板或者需要精细控制样式的场景这些缩写仍然省力。带单位的值也支持显示指定单位。默认数字不加单位时是px这是Emmet的规定如果你想要百分比、rem或em直接在数字后面拼单位首字母。w50%生成width: 50%;fz2r生成font-size: 2rem;lh1.5生成line-height: 1.5;。长度类属性默认px不需要额外声明但lh这类无单位属性直接用数字就好。CSS还有一种比较隐蔽的缩写规则是针对!important的。给属性缩写后加一个感叹号比如c#fff!展开为color: #fff !important;。写覆盖样式时这个很有用不用手动去行尾补感叹号。CSS侧还有一个非常实用的点f和m分别对应font-face和media的规则代码块。输入m后按Tab直接生成media { }配合变量名和选择器比手敲方便很多。还有i生成importkf生成keyframes这些写动画和响应式时都是高频快捷键。4. 实战落地编辑器配置与一套完整的高效工作流4.1 主流编辑器的启用与验证Emmet现在基本成了各大编辑器的标配能力不需要额外安装插件。Visual Studio Code在2020年以后内置了对Emmet的支持默认启用WebStorm和IntelliJ系列自带Sublime Text需要装一个单独的Emmet插件包。这里我以VS Code为例说明配置步骤。第一步打开设置面板搜索emmet确认Emmet: Enabled目录下已经勾选对应语言。默认情况下HTML、CSS、JSX都是启用的如果发现输入缩写按Tab没反应大概率是这个开关被关掉了。第二步为了让缩写能识别更多文件类型可以在Emmet: Include Languages里配置映射比如把vue文件映射到html。第三步展开缩写的默认快捷键在VS Code里是Tab也可以通过命令面板输入Emmet: Expand abbreviation手动触发。不同编辑器的展开键略有差异WebStorm默认也是TabSublime用的是CtrlE。我自己的习惯是把VS Code的展开键保持Tab不动因为Tab本身也是跳转光标用的两者共存时Emmet会优先响应带缩写特征的内容不太冲突。4.2 自定义snippets打造自己的代码片段库内置语法覆盖的是通用场景但真实项目里总有特定结构和固定片段反复出现。比如你所在团队的后台管理系统每个列表页都由搜索栏、表格、分页器三件套组成那就不必每次从头写三层结构可以直接把这段结构做进自定义片段。Emmet支持通过snippets.json扩展自己的缩写。VS Code里的路径是设置面板右上角“打开设置(JSON)”追加一个emmet.extensionsPath字段指向你存放snippets.json的目录。文件内容格式如下{ html: { snippets: { adminList: div.search-bardiv.table-wrappertable.tabletheadtrth*6tbodytr*5td*6div.paginationulli*5 } } }保存后重启编辑器在HTML文件里输入adminList再按Tab整个列表页骨架就出来了。这一步的意义不只是省时间它把团队规范里的“标准结构”直接固化成了工具链的一部分。新同学上手项目时打开snippets.json看一遍就知道列表页该长什么样比看文档直观得多。我自己维护了一个几十条的自定义片段库涵盖表单、弹窗、分页、列表、导航、页脚、面包屑、数据空状态等高频结构。遇到相似的业务模块先调片段生成骨架再手动改类名和文本整个页面从零到出的时间能压到原来的三分之一。4.3 与编辑器其他能力组合的完整工作流要真正让Emmet发挥效率不是孤立地按Tab而是把它跟编辑器的多光标、批量操作、格式化组合起来。我常用的流程是先在脑子里把页面拆成几大块用Emmet分别生成大块的结构缩写然后利用多光标同时编辑多个位置的类名或文本内容最后用格式化命令统一调整缩进。具体举个例子。我要做一个用户列表页面分为搜索区、按钮操作区、表格、分页。分别生成四段结构后表格部分可能需要微调列数和行数直接用多光标把th*6改成th*5或者重新生成一次比在生成后的代码里手动增删列要快得多。另外我强烈建议把Emmet和代码格式化快捷键绑定。无论是VS Code的ShiftAltF还是WebStorm的CtrlAltL展开后的代码缩进层级可能有些乱尤其用了^爬升符号以后一次格式化能让结构恢复清晰同时你也能检查嵌套关系是否符合预期。在写JSX时会用到Emmet的另一个特性展开组件的类名和属性。输入div.cardh3{标题}p{描述}button.btn{确认}再按Tab直接生成JSX结构。VS Code默认对JavaScript和TypeScript文件的Emmet支持是关闭的需要在设置里把emmet.includeLanguages对应项加上javascript: javascriptreact和typescript: typescriptreact。React开发的同学把这一步做了写组件的效率会明显上一个大台阶。5. 常见问题与排查实录那些年踩过的坑5.1 缩写按Tab没反应到底哪里出了问题这是我被问得最多的问题。缩写没展开的原因通常集中在下面几个方面我按出现频率排序第一光标不在正确位置。Emmet展开要求光标停留在缩写末尾也就是紧跟最后一个字符。如果你在单词中间或后面多按了个空格Tab都不会触发展开。这是新手最容易犯的错误解决方法是把光标移到缩写末尾确认两边没有多余空格。第二缩写语法有误。比如用了中文括号、全角符号或者嵌套符号敲错。divulli*3和divulli*3的区别很隐蔽但乘号如果用了中文输入法的*字符Emmet无法识别。另外和混排时要注意逻辑顺序有些看起来合理的缩写实际上从根上就矛盾比如divspanp这个没问题但divspanp^a的爬升层级可能和你预期的不一样。第三文件类型不对。在.vue、.jsx文件里直接输入HTML缩写如果没做includeLanguages映射编辑器不会触发Emmet。在CSS文件里输入w100没反应先去看文件的语法模式是不是CSS有时候VS Code会误判文件类型。第四与代码片段冲突。如果你装了类似“HTML Snippets”、“Auto Complete Tag”这类插件它们的自动补全建议覆盖了某些缩写Tab被插件拦截。排查方法很粗暴暂时禁用其他补全类插件只用Emmet试试如果恢复正常就知道是冲突了。5.2 语法虽强大但别滥用几个反面案例有一种倾向是初学者觉得缩写越长越厉害结果写出行一百多字符的嵌套。比如这种div.wrapperheadernavulli*5a[href#]{Link $}^footerp{版权信息}看上去很酷实际上可读性已经崩了。展开后要检查半天才知道结构什么样如果中间写错一个符号排查成本也不低。我的经验是一组缩写超过二十个符号就拆成两段来写。先写外层的div.wrapperheadernavulli*5展开后再用光标操作补a标签和footer部分。效率不一定慢但准确率高很多。还有一个常见的坑过多依赖Emmet生成后再去大改反而不如直接手敲快。Emmet擅长的是生成重复性、规则性的结构。如果你要的结构每行都不一样类名毫无规律文本内容需要逐个斟酌那直接用多光标或复制粘贴反而更快。工具是放大你的效率不是替代你思考。我见过有人明明要写五个彼此之间差异巨大的表格行硬要用tr*5生成再逐个改那还不如直接写。5.3 输入法冲突和其他环境问题中文输入法在Emmet使用中会出现一个不大不小的问题当你输入缩写后候选词框可能遮挡代码提示或者Tab键被输入法截获用于选词。解决办法是把输入法设为英文模式或者调整编辑器的触发键。VS Code里可以通过修改emmet.triggerExpansionOnTab为false换用CtrlE手动触发。我个人更推荐保留Tab触发因为在英文输入状态下几乎没有冲突。另一个环境问题是老版本编辑器对Emmet的支持不完整。某些轻量级编辑器或在线IDE里Emmet只支持HTML不完全支持CSS缩写或者不支持自定义snippets。如果你在某个环境里发现缩写规则不灵先去查那个环境的文档别在编辑器配置上浪费太多时间。5.4 调试缩写一步步验证生成结果当我需要写一段比较复杂的嵌套结构时我的习惯是先单独验证小型片段再组合。比如想生成一个带图标、标题、副标题和按钮的卡片div.carddiv.card-bodyi.icondiv.card-texth5{标题}p{这里是描述}^a.btn[href#]{查看详情}这段缩写的难点在于^的使用位置。如果^放错a.btn可能变成p的子标签。我的做法是先只写div.carddiv.card-bodyi.icondiv.card-texth5{标题}p{这里是描述}展开确认结构正确再加上^a.btn[href#]。一次只处理一个难点排查起来比对着几十行纯文本缩写发呆快得多。每个编辑器里Emmet扩展出来的代码都会保留原始缩写的缩进层级你可以在展开后先不格式化直接看缩进判断嵌套关系是否正确。养成“缩写生成后先扫一眼缩进”的习惯能避免绝大多数结构错误。6. 进阶技巧Emmet不止是HTML和CSS的玩具6.1 缩写思维在JSX和模板语法中的延伸前面提过JSX支持这里说细一点。在React组件中写列表渲染经常需要构建这样的结构div classNamelist div classNameitem1/div div classNameitem2/div /div如果手动打重复量很大。在配置好includeLanguages之后输入div.listdiv.item*3就能展开成对应的JSX。唯一要注意的是className的展开结果默认是class手动改成className即可或者通过snippets.json自定义一个div.item的变体。Vue的单文件组件里也有类似用法在template部分直接使用HTML缩写在script和style部分使用对应语言的缩写。配置方式同样是includeLanguages映射把vue映射为html。6.2 用Emmet反向训练代码结构感我个人认为Emmet最大的隐藏价值不是打字速度而是它强迫你用极其精炼的方式描述DOM结构。当你习惯了用一行缩写表达一个完整的组件树你在看别人的代码时也会更快抓取层级关系。我面试前端候选人时会让对方写一段带弹窗和表单的页面结构如果候选人能用Emmet快速生成至少说明他理解结构抽象而不仅仅是背标签。新人在学习HTML时我也建议他们故意用Emmet去“描述”自己想写的结构然后展开对比看自己的描述和真实结构之间的差异。这个过程有点像写作时先用提纲搭骨架再填充细节对培养结构化思维很有帮助。6.3 项目中的实际收益和量化感受我统计过自己在两个相似规模的后台页面上的耗时。第一个页面用传统手敲方式写HTML结构差不多花了四十五分钟其中大量时间消耗在重复复制、修改类名和检查标签闭合上。第二个页面在熟悉Emmet之后缩写的构思加展开操作只花了大约十五分钟接近三倍的时间差。虽然内容复杂度不完全一致但量级差距足以说明问题。如果你每天只写两三段小结构感受可能不明显。但当你处理中大型页面、邮件模板、组件库文档这类HTML密集型工作时节省的时间是按小时计算的。而且不要忘了Emmet展开的代码天然没有漏闭合标签的问题这直接减少了一类低级报错和修复成本。7. 写在最后一个老开发者的几点经验建议回头再看这些年在编辑器里敲缩写的时间我最大的感悟是Emmet真正提升的其实是“思路显形”的速度。写HTML最大的痛点不是标签本身而是你想到哪里要写到哪里一旦思路被漫长的标签行打断再找回来就要费不少工夫。用缩写把整个结构一次弹出等于让大脑的思维链路保持连续这对长时间写代码的舒适度影响很大。我给新人的建议很简单别急着背所有语法先记七件事——.类名、#ID、子级、兄弟、*数量、{}文本、[]属性。把这七个符号用熟你已经能解决大部分场景。然后再花半天时间把CSS缩写表过一遍特别是df、jc、ai、bdr这种高频的写页面时的流畅度会有质的提升。如果你愿意更进一步花一个周末整理自己项目里的高频结构做成snippets.json自定义库那相当于给自己装了一套专属的模板引擎。每个人写的代码风格不一样最终打磨出的snippets也不一样这套积累会在以后每个项目里持续产生复利。我自己的snippets文件已经迭代了五个版本每次跳槽、换项目组都会往里补充新结构它已经成了我比收藏夹更宝贵的个人资产。