Emmet: 实战 1:用 Emmet 一气呵成写登录页 HTML(综合 1-16 课)

1-16 课你学会了 Emmet 的 8 大语法、3 个核心 Actions、5 个编辑加速 action——但都是"碎片式"小例子。本课是第一个综合实战:用一行 Emmet 缩写写完一个 70 行的完整登录页 HTML。Alice 用 10 秒写完整个表单,比手写快 10 倍。

1. 你将学到


2. 一个前端开发者的真实故事

(1) 痛点:3 个 input + 2 个按钮 + 3 个链接 + 标题,手写要 10 分钟

Alice 是 SaaS 平台前端开发者。2026 年 7 月产品经理临时通知:本周要给 admin 后台加一个登录页,下周一上线。设计稿在 Figma,已经给到 5 个区块:

区块 元素清单
1. 品牌头部 <span> 显示产品名 "CLOUDFLOW"
2. 标题区 <h1> 欢迎登录 + <p> 副标题
3. 表单 用户名 / 密码 2 个 input + 标签 + 复选框 + 忘记密码链接
4. 提交按钮 主按钮"登录" + 副按钮"Google""GitHub"社交登录
5. 注册引导 底部"还没有账号?立即注册"链接

合计:1 个 <span>、1 个 <h1>、1 个 <p>、2 个 <input>、2 个 <label>、1 个 <button>、1 个 <a> 忘记密码、2 个 <button> 社交登录、1 个 <a> 注册 = 11 个表单元素 + 1 个品牌 span。手写 HTML:

HTML
<span class="brand-name">CLOUDFLOW</span>
<h1 class="title">欢迎登录</h1>
<p class="subtitle">使用账号和密码继续</p>
<form id="loginForm" action="/login" method="post">
  <div class="field">
    <label for="user">用户名</label>
    <input id="user" class="input" type="text" name="user" placeholder="请输入用户名" required>
  </div>
  <div class="field">
    <label for="pwd">密码</label>
    <input id="pwd" class="input" type="password" name="pwd" placeholder="请输入密码" required>
  </div>
  <div class="field-row">
    <label class="checkbox">
      <input type="checkbox" name="remember"> 记住我
    </label>
    <a class="help" href="/forgot">忘记密码?</a>
  </div>
  <button type="submit" class="btn btn-primary">登录</button>
</form>
<div class="divider"><span>或</span></div>
<div class="social">
  <button class="social-btn" type="button">Google</button>
  <button class="social-btn" type="button">GitHub</button>
</div>
<p class="footer">还没有账号? <a href="/signup">立即注册</a></p>

合计 32 行手写 HTML,加上各种属性细节(id / class / type / name / placeholder / required / for / action / method),Alice 估算需要 8-10 分钟——而且容易漏属性(如忘记 required 导致表单不验证)。

(2) Emmet 一气呵成的解法

Alice 打开 VSCode,按 ! 展开 HTML 骨架,然后定位到 <body> 里,输入一行缩写:

HTML
div.login-container>div.login-card>(.brand>span.brand-name{CLOUDFLOW})+h1.title{欢迎登录}+p.subtitle{使用账号和密码继续}+form#loginForm[action=/login method=post]>(.field>label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required])+(.field>label[for=pwd]{密码}+input#pwd.input[type=password name=pwd placeholder="请输入密码" required])+(.field-row>(label.checkbox>input[type=checkbox name=remember]{记住我})+a.help[href="/forgot"]{忘记密码?})+button[type=submit].btn.btn-primary{登录}+.divider{或}+.social>(button.social-btn{Google})+(button.social-btn{GitHub})+p.footer{还没有账号?}+a[href="/signup"]{立即注册}

Tab32 行 HTML 一键展开。Alice 又用了 10 秒——比手写快 48-60 倍。更重要的是:0 漏属性required / placeholder / for 全部带上),零笔误。

(3) 收益

Alice 这次"实战 1"用了 10 秒写完表单,比手写快 48-60 倍;并且 0 漏属性、0 拼写错误。最大收益不是速度,而是减少 80% 的"漏属性" bug——手写时容易忘记 requiredfor,Emmet 缩写时这些细节是缩写的"组成部分",不可能漏。下周一上线前 QA 一次通过,零回归。


3. 登录页元素清单:5 个区块映射 Emmet 操作符

下面这张表把 5 个区块映射到具体的 Emmet 操作符——这是"分解需求"的关键步骤。

区块 元素 Emmet 写法 对应语法(第几课学的)
1. 品牌头部 <span class="brand-name">CLOUDFLOW</span> span.brand-name{CLOUDFLOW} 第 03 课隐式标签 + 第 09 课文本
2. 标题区 <h1 class="title"> + <p class="subtitle"> h1.title{...}+p.subtitle{...} 第 04 课兄弟节点 + 第 03 课 class + 第 09 课文本
3. 表单 <form id="loginForm" action="..." method="..."> 嵌套多个 <div class="field"> form#loginForm[action=/login method=post]>(.field>...) 第 04 课子级 + 第 07 课 id + 第 08 课自定义属性 + 第 06 课分组 + 隐式标签
4. 提交按钮 <button type="submit" class="btn btn-primary">登录</button> button[type=submit].btn.btn-primary{登录} 第 08 课自定义属性 + 第 07 课多 class + 第 09 课文本
5. 社交登录 <div class="social"> 内 2 个 <button> .social>(button.social-btn{Google})+(button.social-btn{GitHub}) 第 06 课分组 + 第 05 课乘法(也可以 *2)+ 隐式标签
📌 重点: 这张表是"Emmet 实战的核心方法"——先把页面拆区块,再把区块拆元素,最后把元素翻译成 Emmet 缩写。3 步思维链,是从"Emmet 初学者"到"Emmet 高手"的关键。


4. 一气呵成的 Emmet 缩写(核心)

下面 3 个示例从最小单元逐步组合到完整表单——演示"一气呵成"的思维过程。

▶ 示例:标题块 h1.title{欢迎登录}(难度⭐)

最小单元:1 个 <h1> 带 class 和文本。

缩写:

HTML
h1.title{欢迎登录}

展开:

HTML
<h1 class="title">欢迎登录</h1>

输出: 渲染为 <h1> 标题,class="title" 可被 CSS 选择器命中(如 .title { font-size: 28px; })。

TEXT 📖 仅展示
解析:`h1` 是元素名(HTML 第 03 课),`.title` 是 class(第 07 课),`{欢迎登录}` 是文本内容(第 09 课)。3 个语法点组合——这是 Emmet 缩写的"最小完整单元"。

▶ 示例:表单字段对 label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required](难度⭐⭐)

表单 1 个字段:<label> + <input>,带 for/id 关联 + 多个属性。

缩写:

HTML
label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required]

展开:

HTML
<label for="user">用户名</label>
<input id="user" class="input" type="text" name="user" placeholder="请输入用户名" required>

输出: 渲染为 1 个 <label> + 1 个 <input><label for="user"> 通过 id="user" 关联到 <input>(点击 label 聚焦 input)。<input> 带 6 个属性:id / class / type / name / placeholder / required

TEXT 📖 仅展示
解析:缩写的"信息密度"远超手写——
- 3 个语法点:`#user`(id)、`.input`(class)、`[type=text]`(属性)
- 5 个属性键值对:`type=text` / `name=user` / `placeholder="请输入用户名"` / `required`(布尔属性,无值)
- 1 个文本:`{用户名}`
共 9 个 HTML 元素属性,缩写字符仅 95 个——手写同样的属性要 200+ 字符。

▶ 示例:完整登录表单(一行核心缩写)(难度⭐⭐)

完整表单:1 个 <form> + 2 个 <div class="field">(每个含 label+input)+ 1 个 <div class="field-row">(含 checkbox+link)+ 1 个 <button>

缩写:

HTML
form#loginForm[action=/login method=post]>(.field>label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required])+(.field>label[for=pwd]{密码}+input#pwd.input[type=password name=pwd placeholder="请输入密码" required])+(.field-row>(label.checkbox>input[type=checkbox name=remember]{记住我})+a.help[href="/forgot"]{忘记密码?})+button[type=submit].btn.btn-primary{登录}

展开:

HTML
<form id="loginForm" action="/login" method="post">
  <div class="field">
    <label for="user">用户名</label>
    <input id="user" class="input" type="text" name="user" placeholder="请输入用户名" required>
  </div>
  <div class="field">
    <label for="pwd">密码</label>
    <input id="pwd" class="input" type="password" name="pwd" placeholder="请输入密码" required>
  </div>
  <div class="field-row">
    <label class="checkbox"><input type="checkbox" name="remember">记住我</label>
    <a class="help" href="/forgot">忘记密码?</a>
  </div>
  <button type="submit" class="btn btn-primary">登录</button>
</form>

输出: 渲染为完整登录表单,2 个 input 字段(用户名 / 密码)+ 1 个 checkbox(记住我)+ 1 个忘记密码链接 + 1 个登录按钮。

TEXT 📖 仅展示
解析:这条缩写串起了 1-16 课的所有核心语法——
- `form#loginForm`:元素名 + id(第 03 + 07 课)
- `[action=/login method=post]`:2 个自定义属性(第 08 课)
- `>(...)`:子级嵌套 + 分组(第 04 + 06 课)
- `.field` / `.field-row`:隐式标签 `div`(第 03 课)
- `label[for=user]{用户名}`:元素 + 自定义属性 + 文本(第 08 + 09 课)
- `input#user.input[type=text ...]`:id + 多 class + 多属性(第 07 + 08 课)
- `+`:兄弟节点(第 04 课)
- `button[type=submit].btn.btn-primary{登录}`:多属性 + 多 class + 文本(第 07 + 08 + 09 课)
共 14 个 HTML 标签、35+ 个属性——手写 200+ 字符,缩写 270 字符展开后 200+ 行手写 HTML。

5. 缩写拆解:每个符号的语法含义

上一节的"完整表单"缩写 270 字符,看一眼头晕。下面把它拆成 5 段,逐段讲每个符号:

缩写片段 语法点 含义
1 form#loginForm[action=/login method=post] 元素 + id + 自定义属性(第 03+07+08 课) <form id="loginForm" action="/login" method="post">
2 >(...) 子级 + 分组(第 04+06 课) 进入 form 内部,包成一个分组
3 (.field>label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required]) 隐式 div + 子级 + 元素 + 属性 + id + class + 文本(第 03+04+07+08+09 课) 1 个 .field 分组:内含 label + input
4 +(.field>label[for=pwd]{密码}+input#pwd.input[type=password name=pwd placeholder="请输入密码" required]) 兄弟节点 + 重复段 3 结构 第 2 个 .field(密码字段),与段 3 兄弟
5 +(.field-row>(label.checkbox>input[type=checkbox name=remember]{记住我})+a.help[href="/forgot"]{忘记密码?})+button[type=submit].btn.btn-primary{登录} 兄弟节点 + 多层分组 + button 多属性 复选框 + 忘记密码 + 登录按钮
📌 重点: 第 5 段是最复杂的——.field-row 内嵌 2 个兄弟(label.checkbox + a.help),再 + 一个 button[type=submit]多层分组 + 兄弟节点嵌套是 Emmet 高阶的核心——熟练后能写"一行 = 20 行 HTML"。


6. 完整示例:登录页一气呵成 70 行

把上面的核心缩写嵌入完整 HTML 页面(含 <!DOCTYPE> / <head> / <style> / <body> 包裹 + 完整 CSS 样式),得到 70 行的完整登录页 HTML。这个示例是本课核心——不要加"试一试"按钮(按规范 v2.5 门禁 #26,综合示例不在编辑器中预览)。

HTML
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>管理后台 - 登录</title>
  <meta name="description" content="SaaS 后台管理登录页">
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, -apple-system, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; }
    .login-container { width: 100%; max-width: 420px; }
    .login-card { background: #fff; padding: 40px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); }
    .brand { text-align: center; margin-bottom: 24px; }
    .brand-name { font-size: 18px; font-weight: 700; color: #2c3e50; letter-spacing: 1px; }
    .title { font-size: 28px; color: #2c3e50; text-align: center; margin-bottom: 8px; font-weight: 700; }
    .subtitle { color: #7f8c8d; text-align: center; margin-bottom: 32px; font-size: 14px; }
    .field { margin-bottom: 20px; }
    label { display: block; color: #34495e; margin-bottom: 8px; font-size: 14px; font-weight: 500; }
    .input { width: 100%; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; transition: border-color 0.2s; }
    .input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15); }
    .field-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; font-size: 14px; }
    .checkbox { display: flex; align-items: center; gap: 8px; color: #7f8c8d; cursor: pointer; }
    .checkbox input { margin: 0; cursor: pointer; }
    .help { color: #3498db; text-decoration: none; }
    .help:hover { text-decoration: underline; }
    .btn { width: 100%; padding: 14px; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s; }
    .btn-primary { background: #3498db; color: #fff; }
    .btn-primary:hover { background: #2980b9; }
    .divider { display: flex; align-items: center; margin: 24px 0; color: #95a5a6; font-size: 13px; }
    .divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: #ddd; }
    .divider span { padding: 0 16px; }
    .social { display: flex; gap: 12px; }
    .social-btn { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 8px; background: #fff; cursor: pointer; font-size: 14px; }
    .social-btn:hover { background: #f8f9fa; }
    .footer { text-align: center; margin-top: 24px; color: #7f8c8d; font-size: 14px; }
    .footer a { color: #3498db; text-decoration: none; font-weight: 500; }
  </style>
</head>
<body>
  <main class="login-container">
    <div class="login-card">
      <div class="brand">
        <span class="brand-name">CLOUDFLOW</span>
      </div>
      <h1 class="title">欢迎登录</h1>
      <p class="subtitle">使用账号和密码继续</p>
      <form id="loginForm" action="/login" method="post">
        <div class="field">
          <label for="user">用户名</label>
          <input id="user" class="input" type="text" name="user" placeholder="请输入用户名" required>
        </div>
        <div class="field">
          <label for="pwd">密码</label>
          <input id="pwd" class="input" type="password" name="pwd" placeholder="请输入密码" required>
        </div>
        <div class="field-row">
          <label class="checkbox">
            <input type="checkbox" name="remember"> 记住我
          </label>
          <a class="help" href="/forgot">忘记密码?</a>
        </div>
        <button type="submit" class="btn btn-primary">登录</button>
      </form>
      <div class="divider"><span>或</span></div>
      <div class="social">
        <button class="social-btn" type="button">Google</button>
        <button class="social-btn" type="button">GitHub</button>
      </div>
      <p class="footer">还没有账号? <a href="/signup">立即注册</a></p>
    </div>
  </main>
</body>
</html>

输出: 浏览器渲染为完整的 SaaS 后台登录页——紫色渐变背景、白色卡片居中、品牌名 "CLOUDFLOW"、欢迎登录标题、用户名/密码表单(带标签关联和 required 验证)、记住我复选框、忘记密码链接、蓝色登录按钮、"或"分割线、Google/GitHub 社交登录、底部注册引导。所有样式在 <style> 中内联,0 外部依赖,0 第三方库。

TEXT 📖 仅展示
解析:本课 70 行 HTML 串起了 1-16 课全部 HTML 简写语法——
- `<style>` 块(25 行 CSS)由开发者手写或用 CSS 简写(第 11-13 课);本课聚焦 HTML 部分
- `<body>` 内 `<main class="login-container">` 到 `</main>` 的 32 行 HTML 由 Emmet 一行缩写展开
- 行数对比:手写 32 行 HTML 用 8 分钟;Emmet 缩写 270 字符 + Tab 展开 10 秒。**速度提升 48 倍,漏属性率从 20% 降到 0%**

🌐 动手试试:Emmet Live Demo 输入本课核心缩写 form#loginForm[action=/login method=post]>(.field>label[for=user]{用户名}+input#user.input[type=text name=user placeholder="请输入用户名" required])+(.field>label[for=pwd]{密码}+input#pwd.input[type=password name=pwd placeholder="请输入密码" required])+button[type=submit].btn.btn-primary{登录},按 Tab 键,观察实时展开效果。


❓ 常见问题

Q Emmet 缩写里能写中文文本吗?
A 能,但有限制。文本内容({text} 内)可以写任意字符——包括中文、emoji、空格。但属性值([attr=val] 内)如果有空格必须加引号包裹(如 [placeholder="请输入用户名"])。不建议在 id/class 写中文——CSS 选择器对中文 id/class 兼容性差(IE 旧版不支持),强烈建议 id/class 用英文。
Q placeholder 这种长属性名需要写全吗?
A Emmet 不支持属性名简写——[placeholder=...] 必须写全。但属性值可以省略引号(无空格时):[type=text] 等价于 [type="text"][placeholder=请输入用户名] 这种"带空格"的属性值必须加引号:[placeholder="请输入用户名"],否则 Emmet 解析会出错。
Q 表单提交后跳转到哪里?
A 本课示例中 action="/login" 表示提交到后端的 /login 接口。真实开发中,action 一般是后端路由(如 Express 的 app.post('/login', ...)、Django 的 path('login/', views.login))。本课聚焦 HTML 部分,后端处理不展开。如果你要纯前端 mock,可以用 <form>action="javascript:void(0)" 加 JS 监听 submit 事件拦截。
Q 为什么登录表单需要 required 属性?
A required 是 HTML5 引入的"必填验证"——浏览器原生检查输入框是否为空,空值时阻止提交并提示"请填写此字段"。本课示例中用户名/密码 2 个 input 都有 required,忘记密码链接不需要(不是表单字段)。这是"前端零 JS 验证"的关键属性,强烈建议所有必填字段都加。手写容易漏,Emmet 缩写时是"结构化必填项",漏的概率为 0。

📖 小节


📝 作业

  1. 基础题(难度⭐):用一行 Emmet 缩写写 1 个简单登录页骨架——包含 1 个 <h1 class="title">用户登录</h1>、1 个用户名 <input>(带 id="email"type="email"name="email"required)、1 个密码 <input>(带 id="pwd"type="password"name="pwd"required)、1 个 <button type="submit">登录</button>。展开后验证:(a) <input>type 属性正确;(b) <button>type="submit" 在 form 内会触发表单提交;(c) 2 个 required 都生效(空值时浏览器阻止提交)。
  2. 进阶题(难度⭐⭐):把本课 70 行完整示例的 Emmet 缩写自己重写一遍——不需要和 Alice 的完全一致,但功能等价:至少 1 个 form、2 个 input、1 个 checkbox、1 个忘记密码链接、1 个登录按钮、1 个注册链接。再加 2 个社交登录按钮(Google、GitHub)。对比你写的缩写 vs Alice 的缩写,看哪个更简洁(字符数更少)。截图保存你的缩写和展开后的 HTML。
  3. 挑战题(难度⭐⭐⭐):把本课 70 行登录页扩展为完整注册页——除了登录页的字段外,增加:手机号(type="tel")、邮箱(type="email")、验证码(type="text" + 右侧"获取验证码"按钮)、用户协议 checkbox(必勾选)。要求:(a) 整页 HTML 一气呵成(一行 Emmet 缩写写完 body 部分);(b) 缩写字符数 ≤ 400(超过的话考虑用 * 乘法或简化结构);(c) 所有 input 都有 required;(d) 用户协议 checkbox 必须勾选才能提交(用 <input type="checkbox" required> 实现,浏览器原生验证)。最后统计手写 vs Emmet 的字符数和时间差,写一份"Emmet 综合实战收益报告"。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏