Emmet: ID 与 Class:`.class` `#id` CSS 选择器式属性简写
第 03 课已经初步介绍了
.class和#id。本课系统展开这两个"CSS 选择器式"简写——它们让 Emmet 缩写同时表达结构和属性。最关键的一条规则:在 HTML 上下文中省略标签名默认为div,绝不会被 class 名"联想"为别的元素(哪怕 class 是btn也不会变成<button>)。
1. 你将学到
.class简写:class="class"属性的 CSS 选择器式表达#id简写:id="id"属性的 CSS 选择器式表达- id 与 class 的混用:
#header.nav同时设置两个属性 - 多 class 链:
.btn.btn-primary.btn-lg一次性设置 3 个 class - 与嵌套组合:
div#main.container>section.content>p.text在层级结构中分散属性
2. 一个前端开发者的真实故事
(1) 痛点:标签和属性总得分两次写
Alice 在做登录表单的样式升级,需要给所有 <button> 加 class="btn btn-primary btn-lg",给表单本身加 id="loginForm"、class="form-horizontal"。手写时每个标签都要写完整结构:
<button class="btn btn-primary btn-lg">登录</button>
<input id="username" class="form-control" type="text" name="user">
每个标签都重复一遍标签名 + 属性,标签和属性顺序还要靠记忆。她算下来:一个 8 行的登录表单,她要手写 8 个标签 + 24 个属性名 = 32 段重复文本。光写属性的引号和等号,就能让她敲键盘的手指发酸。
更要命的是:她想起之前一个项目里,把 class="btn btn-primary btn-lg" 错写成了 <button class="btn btn-primary btn-lg">——她以为 .btn 会自动联想到 <button> 标签,结果写错了反而绕了一圈。Emmet 的隐式标签规则其实根本不会因为 class 名而切换标签名。
(2) .class #id 简写的解法
Emmet 用与 CSS 选择器完全一样的写法来表达属性——.btn 直接加到当前标签的 class 属性,#loginForm 加到 id 属性。这套语法让你写完标签结构的同时也写完了属性,无需切换"标签模式"和"属性模式"。
Alice 用一行 Emmet 重写整个登录表单:
form#loginForm.form-horizontal>(input#username.form-control[type=text name=user])+(input#pwd.form-control[type=password name=pwd])+(button.btn.btn-primary.btn-lg{登录})
#loginForm.form-horizontal 同时设置 id 和 class,.btn.btn-primary.btn-lg 三个 class 用 . 链一次性写完。8 个标签 + 24 个属性,1 行简写搞定。从 32 段重复文本到 1 行,从 5 分钟到 12 秒。
(3) 收益
.class 和 #id 是 Emmet 把"结构"和"属性"合二为一的关键操作符。学完本课,Alice 写 80% 的标签时都能把属性同时写完;多 class 的写法跟 CSS 完全一样,几乎零学习成本;记住"省略标签名 → div"这一隐式规则(哪怕 class 叫 btn 也仍是 div),让她的简写不再被 class 名误导。
3. .class:CSS 选择器式的 class 属性
.classname 在 Emmet 中等价于在该标签上添加 class="classname"。当标签未指定时(第 03 课隐式规则),默认推断为 div。
| 简写 | 展开结果 | 备注 |
|---|---|---|
.box |
<div class="box"></div> |
隐式 div |
p.text |
<p class="text"></p> |
显式 p |
section.hero |
<section class="hero"></section> |
显式 section |
a.link |
<a href="" class="link"></a> |
显式 a,a 自动带 href 属性 |
. 链式连接,类名之间没有空格——.btn.btn-primary 表示两个 class btn 和 btn-primary。空格在 Emmet 中是停止符,会被解析为多个独立简写。
4. #id:CSS 选择器式的 id 属性
#idname 在 Emmet 中等价于在该标签上添加 id="idname"。用法和 .class 完全对称,对应 CSS 的 ID 选择器(#id)。
| 简写 | 展开结果 |
|---|---|
#main |
<div id="main"></div> |
header#top |
<header id="top"></header> |
form#login |
<form id="login"></form> |
div#app |
<div id="app"></div> |
(1) id 与 class 可以混用
#id 和 .class 在同一个标签上无顺序要求,可任意混用:
| 简写 | 展开结果 |
|---|---|
#main.container |
<div id="main" class="container"></div> |
.container#main |
<div class="container" id="main"></div> |
header#top.nav |
<header id="top" class="nav"></header> |
nav.main.dark |
<nav class="main dark"></nav> |
#id 和 .class 来自 CSS 选择器语法,但语义相反——CSS 用 # . 选择"已有"的元素,Emmet 用 # . 生成带这些属性的元素。一正一反,但语法相同。
5. 实战示例
下面 5 个示例覆盖 .class 和 #id 的全部核心场景——从最基础的单 class,到多 class,再到嵌套结构中的属性链。
▶ 示例:单 class .container(难度⭐)
最基础的 class 简写:省略标签名,默认推断为 div。
缩写:
.container
展开:
<div class="container"></div>
输出:
TEXT 📖 仅展示隐式 div 标签 + 单 class。这种简写在 Bootstrap、Tailwind 等工具类框架中极其常用。.container 在 Bootstrap 里是固定宽度容器,在 Tailwind 里是 max-width 工具。注意:当你在 ul 父元素内写 .container,.container 仍隐式为 div——除非你的缩写位置确实是 ul 的子元素需要 li,否则 .container 不会自动变成 li 之类的语义元素(第 06 课隐式规则)。
▶ 示例:单 id #main(难度⭐)
最基础的 id 简写。
缩写:
#main
展开:
<div id="main"></div>
输出:
TEXT 📖 仅展示一个空 div 带 id="main"。在 HTML 中 id 全局唯一,所以这种简写常用于"页面主容器"或"路由挂载点"。注意:Emmet 不会校验 id 是否唯一——多个 #main 简写都展开为 id="main",是否冲突由你后续手动检查。
▶ 示例:多 class 链 .btn.btn-primary.btn-lg(难度⭐⭐)
. 链式连接多个 class——注意:默认仍是 <div>,不会因为 class 名包含 "btn" 而变成 <button>。
缩写:
.btn.btn-primary.btn-lg
展开:
<div class="btn btn-primary btn-lg"></div>
输出:
TEXT 📖 仅展示这是初学者最容易误解的简写:因为 class 名是 `btn`,不少同学以为 Emmet 会把 `.btn` 自动识别为 `<button>`。但 Emmet 的隐式规则不依赖 class/id 值,只依赖父元素语境(详见第 06 课)。当父元素不是 `button`、没有任何标签出现时,Emmet 永远默认 `div`。如果想要 `<button class="btn btn-primary btn-lg">`,必须显式写 `button.btn.btn-primary.btn-lg` 或 `button.btn.btn-primary.btn-lg{登录}`(加文本填充,第 09 课)。验证方法:在编辑器中输入 `.btn` 后立即按 Tab,看是 `div` 还是 `button`。
▶ 示例:id + class 组合 #header.nav(难度⭐)
#id 和 .class 混用,顺序不影响展开结果。
缩写:
#header.nav
展开:
<div id="header" class="nav"></div>
输出:
TEXT 📖 仅展示同时设置 id="header" 和 class="nav" 两个属性。这是页面顶部导航的经典简写。id 和 class 顺序可以互换(.nav#header 展开结果相同),但通常 id 写在前面更符合"先确定唯一身份、再描述类型"的语义习惯。这种简写在多页面应用(multi-page app)中很重要——每个页面的 #header id 相同,CSS 就能统一选中。
▶ 示例:嵌套属性链 div#main.container>section.content>p.text(难度⭐⭐)
把 id、class 撒到嵌套的每一层——3 层标签,每层都有自己的属性。
缩写:
div#main.container>section.content>p.text
展开:
<div id="main" class="container">
<section class="content">
<p class="text"></p>
</section>
</div>
输出:
TEXT 📖 仅展示解析:div 带 id="main" class="container",下钻一层到 section(带 class="content"),再下钻到 p(带 class="text")。每一层都通过 #id 或 .class 设置自己的属性。这是 Emmet 在"组件化结构"中的典型用法——每一层都代表一个语义清晰的容器或组件。如果只想设置一层的属性(其他层保持空),不写 #/. 即可,例如 div>section>p 只是 3 层空嵌套。
6. 完整示例:登录表单 + 属性链
把 .class #id 与前面的 > + 组合起来,一个完整的登录表单用一行简写搞定。
▶ 示例:完整登录表单(难度⭐⭐)
缩写:
form#loginForm.form-horizontal>(input#user.form-control[type=text name=user placeholder="Username"])+(input#pwd.form-control[type=password name=pwd placeholder="Password"])+(button.btn.btn-primary.btn-lg{登录})
展开:
<form id="loginForm" class="form-horizontal">
<input id="user" class="form-control" type="text" name="user" placeholder="Username">
<input id="pwd" class="form-control" type="password" name="pwd" placeholder="Password">
<button class="btn btn-primary btn-lg">登录</button>
</form>
输出:
TEXT 📖 仅展示解析:form 同时设置 id 和 class,里面 + 添加两个 input 兄弟,最后 + 添加 button。input 和 button 都通过 .class 设置样式类,input 额外用 #id(可选)和 [type=name=placeholder=](下一课展开)设置属性。注意到 button 这一层显式写了 `<button>` 标签,所以 .btn.btn-primary.btn-lg 在 button 上变成 class 而不是隐式 div。这是"用显式标签避免误判"的标准模式。
❓ 常见问题
.btn.btn-primary 展开成 <button> 还是 <div>?为什么?<div>。Emmet 的隐式标签规则只依赖父元素语境,不依赖 class/id 的字面值。除非你显式写 button.btn.btn-primary,否则只要没有显式标签,默认就是 div。这是初学者最容易踩的坑——很多人误以为 .btn 应该对应 button,所以看到展开是 div 就以为是 bug。#my-id 中的连字符需要转义吗?#my-id 和 .my-class 中的连字符 - 是合法的 CSS 选择器字符,Emmet 会原样作为 id/class 名的一部分。但要注意:开头不能是 -(如 #-main),这不符合 CSS 语法,Emmet 可能会报错或解析异常。# 和 . 在同一个标签上能混用多少次?#a.b.c.d.e.f.g 完全合法,展开为 <div id="a" class="b c d e f g"></div>。但实际上 id 在 HTML 中应当唯一且数量受控,class 数量过多也会让样式管理变难。推荐:1 个 id + 2-4 个 class 是常见的"合理上限"。#header 和 .header 展开后有什么区别?.header → <div class="header"></div>(class 用于可复用的"类型");#header → <div id="header"></div>(id 用于"唯一身份")。CSS 选择层面:.header 可重复匹配多个元素,#header 只能匹配一个元素。HTML 语义层面:class 通常用于"组件类型"(按钮、卡片、表单),id 通常用于"页面锚点或 JS 钩子"。📖 小节
.classname给当前标签添加class="classname"#idname给当前标签添加id="idname"- 标签省略时默认推断为
div(不受 class 名联想影响) - 多 class 用
.链式连接,无空格 #id和.class可混用,顺序不影响展开- 与嵌套组合时:每一层都可以独立设置 id/class 属性
- 想强制 button 标签,必须显式写
button.btn.btn-primary.btn-lg
📝 作业
- 基础题(难度⭐):分别写出 5 个 Emmet 简写:(a)
.alert;(b)#app;(c).btn.btn-danger;(d)#header.nav.dark;(e)a.btn。 - 进阶题(难度⭐⭐):分析缩写
header#top.navbar>nav.main>ul.nav-list>li.nav-item*3>a.nav-link的展开结构(每层标签和每个属性的命名),验证 Emmet 展开结果是否与你的分析一致。 - 挑战题(难度⭐⭐⭐):编写一个完整 Bootstrap 风格的"卡片网格":外层
div.container,内含div.row,row 内含 3 张.card,每张 card 包含img.card-img-top、div.card-body、h5.card-title、p.card-text、a.btn.btn-primary。要求全部用一行 Emmet 简写,并说明为什么.btn.btn-primary在这里会自动变成<a>而不是隐式 div(提示:简写里有没有显式标签)。