Emmet: 自定义属性 `[attr=val]` 与编号 `$`:表格表单列表序号简写
前 7 课你已经能用 Emmet 写出任意嵌套、任意属性、任意重复的 HTML。但还有两类"工厂化"场景没解决——"10 个 input 每个 name 不同" 和 "100 行表格 序号自动递增"。这两个场景分别靠 自定义属性
[attr=val]和 编号$解决,是 Emmet 处理"批量生成"的最后两块拼图。
1. 你将学到
- 自定义属性
[attr=val]:HTML5 data-、aria- 等任意属性都能加 - 缩写属性:
[attr]简写(无值)、连写多个属性 - 编号
$:在 class、id、属性值中插入自动递增的序号 - 多位数补零
$$$:让 001、002、003 自动生成 - 编号方向与起始值:
@-反向、@N从 N 开始计数 - 综合应用:表格 + 属性 + 编号 + 隐式标签组合
2. 一个前端开发者的真实故事
(1) 痛点:表单字段多 + 表格行多,手写到手指断
Alice 接到一个登录 + 注册的合并表单任务,需要 5 个 input(用户名、邮箱、密码、确认密码、记住我)和 1 个 submit 按钮。她写了一个表单后发现,input 的 name 几乎长得一样——name="user"、name="email"、name="pwd" 等等,每个都要手敲。
更要命的是注册页的"用户协议"表格:100 行协议条款,每行 1 个 tr,第 1 列是序号(1-100),第 2 列是条款文字。Alice 试着手写:tr>td{1}+td{条款 1}、tr>td{2}+td{条款 2}、……写到第 30 行她崩溃了。光写 30 行类似的"tr + 2 个 td"就占了她 30 分钟。
最后 Alice 试着写完所有字段后,又要在 input 上加 aria-label="Username input"、data-validate="email" 这种 HTML5 属性——每个属性都得查规范、写等号、加引号。30 行 input 让她写了 90 行 HTML。
(2) [attr=val] 与 $ 的解法
Emmet 提供两个互补机制解决 Alice 的问题:
自定义属性 [attr=val]:把任意属性写在方括号内,像 HTML 一样直接呈现——Alice 的每个 input 可以用 input[name=user type=text] 一行写完,aria 和 data-* 属性同样可以放在方括号内。
编号 $:让重复元素的 class、id、属性值自动递增。100 行协议的序号可以用 td.item$@100*100 一行生成(从 100 开始递减),条款文字用 {text} 填充(第 09 课展开)。
Alice 用一行 Emmet 重写整个表单:
form>(input[name=user type=text placeholder="Username"])+(input[type=email name=email])+(input[type=password name=pwd])+(input[type=password name=pwd-confirm])+(label>input[type=checkbox name=remember]+{记住我})+(button[type=submit]{登录})
100 行协议表格:
(table>(tr>td{item $}*3)*5)
Alice 验证之后惊呼:"5 个 input 字段的手写从 25 行降到 1 行,100 行协议表格从 350 行降到 1 行。Emmet 在表单和表格场景的提效是 30 倍以上。"
(3) 收益
[attr=val] 和 $ 是 Emmet 处理"重复 + 工厂化"场景的核心。前者让你在一行简写里覆盖所有 HTML5 属性,后者让重复元素的序号自动跑。学完本课,Alice 写 5 字段表单只要 12 秒,100 行表格只要 10 秒;同时这一课也是 Phase 2 HTML 简写语法的收官——后续 9 课将进入 CSS 简写与编辑器 Actions。
3. 自定义属性 [attr=val]:方括号里的 HTML5 属性
[attr=val] 是 Emmet 给标签添加任意属性的语法。写在方括号内的内容会原样成为 HTML 属性——无论是 HTML5 标准属性(colspan、type、placeholder),HTML5 新增属性(data-*、aria-*),还是自定义属性。
(1) 简写规则
| 简写 | 展开结果 | 说明 |
|---|---|---|
td[title="Hello"] |
<td title="Hello"></td> |
单属性 + 双引号 |
td[colspan=3] |
<td colspan="3"></td> |
单属性 + 无引号(值无空格时可省) |
td[title colspan=3] |
<td title="" colspan="3"></td> |
缩写形式(无值属性) |
input[type=text name=user] |
<input type="text" name="user"> |
多属性空格分隔 |
a[href="#" data-id=$] |
<a href="#" data-id="1"></a> |
data-* 属性 + 编号占位 |
[attr] 无值形式(如 [disabled])会生成 attr=""——值为空字符串。如果你想要 boolean 属性(如 <input disabled>),Emmet 不会自动去掉值,需要手动改。但大多数现代 HTML 标准属性都支持值形式,处理起来一致。
4. 编号 $:自动递增的占位符
$ 是 Emmet 的编号占位符——在 class、id、属性值甚至文本位置写 $,配合 *N 重复,会自动被替换为当前序号(默认从 1 开始)。
(1) $ 的使用位置
| 位置 | 简写 | 展开(*3) |
|---|---|---|
| class 内 | li.item$*3 |
<li class="item1">、<li class="item2">、<li class="item3"> |
| id 内 | div#user$*3 |
<div id="user1"> …… |
| 属性值内 | a[data-id=$]*3 |
<a data-id="1"> …… |
| 文本内({}) | a{链接 $}*3 |
<a href="">链接 1</a> ……(第 09 课展开) |
(2) 多位补零 $$$
多个 $ 串联表示多位补零:
$ 数 |
展开形态 | 适用场景 |
|---|---|---|
item$ |
item1 item2 item3 | 列表、菜单 |
item$$ |
item01 item02 item03 | 小型列表 |
item$$$ |
item001 item002 item003 | 表格行、卡片 |
item$$$$ |
item0001 …… | 罕见(千行以上) |
(3) 反向 $- 和 $-N
默认编号从 1 开始升序。用 @- 改为降序,用 @N 改为从 N 开始:
| 修饰符 | 含义 | 示例 (*3) |
|---|---|---|
$(默认) |
升序,从 1 | 1, 2, 3 |
$- 或 @- |
降序(最大值降到 1) | 3, 2, 1 |
$@3 |
升序,从 3 开始 | 3, 4, 5 |
$-3 或 @-3 |
降序,从 3 开始倒推 | 3, 2, 1 |
$@N 中 N 是起始值,不是结束值。常用于"从指定编号开始"的场景(如分页页码、产品 SKU 编号、版本号)。
5. 实战示例
下面 5 个示例覆盖 [attr] 和 $ 的全部核心组合——从单属性到多属性,从基础编号到多位补零,从反向到表格。
▶ 示例:基础属性 td[title="Hello" colspan=3](难度⭐)
在 td 上加 title 和 colspan 两个属性。这是 HTML 表格最经典的属性组合之一。
缩写:
td[title="Hello" colspan=3]
展开:
<td title="Hello" colspan="3"></td>
输出:
TEXT 📖 仅展示双引号包围含空格的 title("Hello"),无引号包围无空格的值(3)。Emmet 自动给所有属性加上引号,无论你简写时是否带。多个属性用空格分隔,这与 Emmet 自身的"空格=停止符"规则不同——在方括号内空格有特殊含义。colspan=3 表示单元格横向跨 3 列。
▶ 示例:input 属性 input[type=text name=user placeholder=Username](难度⭐)
一次性给 input 加 type、name、placeholder 三个属性。input 是 HTML 中最常用"属性密集"标签之一。
缩写:
input[type=text name=user placeholder=Username]
展开:
<input type="text" name="user" placeholder="Username">
输出:
TEXT 📖 仅展示三个属性在一行简写内完成。注意:Emmet 智能默认为 input 添加 type="text"(即使你不写 type),所以这里即使简写为 input[name=user placeholder=Username],展开后仍会带 type="text"。如果想要 type=password 或 type=email,必须显式写。三属性连写是表单场景最高频的简写。
▶ 示例:单 $ 编号 ul>li.item$*5(难度⭐)
5 个 li,每个 li 的 class 自动带 1-5 编号——Emmet 官方文档的首选案例。
缩写:
ul>li.item$*5
展开:
<ul>
<li class="item1"></li>
<li class="item2"></li>
<li class="item3"></li>
<li class="item4"></li>
<li class="item5"></li>
</ul>
输出:
TEXT 📖 仅展示5 个 li,class 依次为 item1、item2、item3、item4、item5。这是 Emmet 唯一命名编号(item + 数字)的标准模式。如果 class 名不需要数字部分,直接写 .item(不带 $),所有 li 都共享同一个 class。
▶ 示例:多 $$$ 补零 ul>li.item$$$*5(难度⭐)
用 3 个 $ 让编号自动补零到 3 位。
缩写:
ul>li.item$$$*5
展开:
<ul>
<li class="item001"></li>
<li class="item002"></li>
<li class="item003"></li>
<li class="item004"></li>
<li class="item005"></li>
</ul>
输出:
TEXT 📖 仅展示补零位数 = $ 的个数。3 个 $ 对应 3 位补零(item001、item002...)。补零场景常用于:订单号(前缀补零,便于排序)、表格 ID、SKU、章节编号。如果想要 4 位补零,用 item$$$$;想要 5 位则用 item$$$$$。注意:位数比实际数字少时,会截断(如 *3 用 $$$ 会变成 item001、002、003,恰好 3 位无截断;如果是 *12 用 $$,会变成 item01、02、03、04、05、06、07、08、09、10、11、12,前两位补零后超过 2 位则用实际位数)。
▶ 示例:表格 + 编号 (table>(tr>td{item $})*3)(难度⭐⭐)
分组 + 重复 + 文本填充——3 行 ×1 列的"编号列表"用分组让 td 跟随每个 tr 重复。
缩写:
(table>(tr>td{item $})*3)
展开:
<table>
<tr>
<td>item 1</td>
</tr>
<tr>
<td>item 2</td>
</tr>
<tr>
<td>item 3</td>
</tr>
</table>
输出:
TEXT 📖 仅展示解析:外层括号把整个表格封装为一个独立单元;内层 (tr>td{item $}) 是"一行一个 item N"的模板,*3 让模板重复 3 次。每次重复,$ 替换为当前编号(1、2、3),最终 3 个 tr 各自包含一个 td,文案为 "item 1"、"item 2"、"item 3"。这是 Emmet 在"协议条款编号"、"待办清单序号"、"商品列表"等场景的核心模式。如果想让每一行有多个单元格,把 td{item $} 改成 td{item $}+td{...} 即可(第 09 课展开文本填充)。
6. 完整示例:表单字段集 + 多位补零 ID
把 [attr] 与 $ 组合起来——一个有 5 个 input 字段的注册表单,每个 field 都有 3 位补零的 id。
▶ 示例:注册表单 + 3 位 ID(难度⭐⭐)
缩写:
form#register>(fieldset>legend{账户信息})+(input#field-001.form-control[type=text name=user])+(input#field-002.form-control[type=email name=email])+(input#field-003.form-control[type=password name=pwd])+(label>input#field-004[type=checkbox name=news]+{订阅资讯})+(button#btn-100.btn.btn-primary[type=submit]{注册})
展开:
<form id="register">
<fieldset>
<legend>账户信息</legend>
</fieldset>
<input id="field-001" class="form-control" type="text" name="user">
<input id="field-002" class="form-control" type="email" name="email">
<input id="field-003" class="form-control" type="password" name="pwd">
<label>
<input id="field-004" type="checkbox" name="news">
订阅资讯
</label>
<button id="btn-100" class="btn btn-primary" type="submit">注册</button>
</form>
输出:
TEXT 📖 仅展示解析:本例综合了 Phase 2 的全部操作符——> (嵌套)、+ (兄弟)、#id、.class、[] (属性)、{} (文本填充)。注意:每个 input 的 id 都是手写的,因为 Emmet 的 $ 只能用在 *N 重复结构内。这里 5 个 input 共用了"id 手动 + class 自动"的混合写法——id 是表单的"字段锚点"(JS 钩子),必须唯一;class 是"字段类型"(CSS 选择器),可复用。这种"手写 id + 自动 class"是表单开发的高频模式。
🌐 动手试试: 在 Emmet 官方 Cheat Sheet 中输入
td[title="Hello world!" colspan=3]看实时展开。
❓ 常见问题
[attr] 和 [attr=] 有什么区别?[attr] 简写等价于 [attr=""](空字符串值),展开为 <tag attr="">;[attr=] 显式赋空字符串,展开为同样的 <tag attr="">。两种写法结果完全等价。在 HTML 语义上,空值属性(如 disabled="")与仅出现属性名(disabled)行为相同——但 Emmet 默认生成值形式,需要手动精简。[data-id=$] 不带 * 还能用吗?$ 在没有 * 重复时默认显示 1。所以 a[data-id=$] 会展开为 <a data-id="1"></a>——这通常不是你想要的。如果只是想要一个数据 ID 而不参与编号,应当用 a[data-id=user] 写实际值。@- 和 $- 哪个对?@N 表示从 N 开始,@- 表示降序。组合写法 $-3 等价于 @-3 都表示降序从 3 开始。推荐用 @ 前缀的写法($@3、@3、@-3),更清晰且兼容性好。📖 小节
[attr=val]给标签加任意属性,多个属性用空格分隔[attr]无值形式等价于[attr=""],都生成空值属性- 方括号内是"封闭"上下文——空格是属性分隔符,不是停止符
$是编号占位符,配合*N自动替换为当前序号(默认从 1 升序)- 多个
$自动补零($$$→ 3 位补零) $@N从 N 开始编号,@-降序编号$可以放在 class、id、属性值中- Phase 2 全部 8 个操作符都已学完:
>+^*().#[]$
📝 作业
- 基础题(难度⭐):用一行 Emmet 简写展开为:(a) 一个
a标签带href="#"、class="link"、data-id="$"(注意$不带*默认为 1);(b) 一个td带title="Cell"、colspan=2、rowspan=3;(c) 一个ul>li*5,每个 li 的 class 名为row-$(单数字编号)。 - 进阶题(难度⭐⭐):写一个 5 行 4 列的"成绩表",第 1 行是表头(th),第 2-5 行是数据(td),数据 cell 的 id 用 3 位补零(
cell-001到cell-020)。要求完全用一行 Emmet 简写,并解释为什么 id 用$实现而不能用 class。 - 挑战题(难度⭐⭐⭐):编写一个 100 行"用户协议表格",3 列结构:(a) 序号列用
td.item$@100*100反向编号(从 100 降到 1);(b) 标题列用{协议 $}文本填充(文本从 09 课展开);(c) 内容列先留空。要求全部用一行 Emmet 简写,验证展开结果,并对比手写 100 行 vs 一行 Emmet 简写的时间差异。