读完这篇,你能做什么
能保存并打开一个完整本地页面,用开发者工具检查结构和样式。
学习理由、准备要求与知识点
为什么学它
后端接口需要与页面结构和表单协作,先理解浏览器实际展示与提交什么。
先读这些内容
知识点清单
- HTML 元素与属性
- 语义标题链接按钮
- UTF-8 和 viewport
- CSS 选择器
- 盒模型和 border-box
- 层叠和计算样式
- label 与键盘访问
- 窄屏与溢出检查
运行前先确认页面采用浏览器标准 HTML/CSS;Java 17 只用于研究服务端输出边界与层叠算法。源码定位 OpenJDK 17u jdk-17.0.16+8 的 Swing HTMLWriter,它是 HTML 序列化器,不是现代浏览器排版引擎。算法限定同一来源、同一层、同一属性的普通选择器比较。
01先弄懂它是什么
网页可以先分成三部分来学:HTML 说明内容是什么,例如标题、链接、输入框和按钮;CSS 决定颜色、间距和布局;浏览器默认行为与 JavaScript 决定操作后的变化。只把文字画得像按钮,并不会自动获得键盘操作、焦点和表单提交功能。所以先选用途正确的 HTML 元素,再调整外观。
例如做一张学习记录卡片,可以在 main 中放 h1 标题、说明段落,以及有 label 的输入框。浏览器会把 HTML 解析成节点树,父子关系会影响样式和辅助技术读取。解析结果不一定和源文件逐字相同,浏览器会按规则处理省略或嵌套错误。初学先写完整文档、UTF-8、语言和视口声明,再到开发者工具检查实际 DOM。
同一个页面元素可能同时符合多条 CSS 规则,浏览器需要决定哪些设置生效。先找出适用规则,再按层叠次序选择属性值,结合继承和默认值形成计算样式。不是最后写的规则总能赢:只有排在前面的比较条件相同,才按书写先后决定。盒模型把一个元素分成内容、内边距、边框与外边距;使用 border-box 时,指定宽度包含内容、内边距和边框,外边距仍要另外计算。
文中这几个词,先认一认
- DOM
- 浏览器解析 HTML 时逐步建立的节点树,
JavaScript通过它查找和修改页面内容。 - 层叠
- 多条 CSS 都想设置同一个属性时,浏览器按来源、重要性、层等规则选出最终值。
- 特异性
- 当来源、重要性和层等更优先的规则相同后,再比较选择器的优先级。它按几组计数依次比较,不能随意相加成一个总分。
- 文本转义
- 把数据中的 < 等特殊字符按文本编码,避免用户文字被浏览器当成 HTML 结构。
02看看它怎么工作
浏览器先获取文档并构建 DOM,CSS 规则被解析后与节点匹配。对学习卡片的 color 属性,元素选择器、类选择器和 ID 选择器可能同时命中;层叠先处理规则所属来源和重要性等因素,再在适用范围比较特异性与顺序。不能用一个脱离来源和层的数字解释全部 CSS 行为。
选出声明后,还要得到计算值并参与布局与绘制。一个卡片声明 width:300px、padding:20px、border:1px,采用 content-box 时其边框盒宽度为 342px;采用 border-box 时边框盒为 300px,内容宽度相应缩小。这个可手算例子比盲目增加 max-width 更容易解释窄屏为什么产生溢出。
数据进入 HTML 时则有另一条边界。商品名包含小于号并不代表它想创建新标签;服务端模板或前端 textContent 应把它作为文本处理。HTML 文本、属性、URL、脚本和 CSS 上下文需要不同编码与校验,不能把一种替换函数当万能 XSS 防线。保留内容语义与语法边界,才能让显示结果和业务意图一致。
先跑通一个最小例子
新建一个空练习目录,将下面完整内容保存为 UTF-8 的 index.html,再用浏览器打开。先观察语义元素、盒模型与键盘焦点;本例没有后端,也不会提交或保存数据。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>我的第一张学习卡片</title>
<style>
* { box-sizing: border-box; } /* 指定宽度包含内边距与边框 */
body { margin: 0; padding: 24px; font: 16px/1.7 system-ui; }
main { max-width: 480px; margin: auto; padding: 20px; border: 1px solid #789; }
label, input { display: block; }
input { width: 100%; padding: 8px; margin: 8px 0 16px; }
button { padding: 8px 16px; }
:focus-visible { outline: 3px solid #245db5; outline-offset: 3px; }
</style>
</head>
<body>
<main>
<h1>今日学习</h1>
<p>HTML 描述结构,CSS 描述呈现。</p>
<label for="topic">今天学习的主题</label>
<input id="topic" name="topic" value="Java 集合">
<button type="button">按钮行为将在下一篇添加</button>
</main>
</body>
</html>核对运行结果页面显示一个标题、说明、带标签的输入框和按钮。按 Tab 能依次聚焦输入框与按钮;把窗口缩窄后,卡片仍在窗口内。按钮目前没有保存行为,这是 HTML/CSS 示例的明确边界。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
A["HTML 内容与可信模板"] --> B["解析为 DOM 节点树"]
C["CSS 规则"] --> D["匹配候选声明"]
B --> D
D --> E["按层叠条件选择属性值"]
E --> F["计算样式与盒模型"]
F --> G["布局与绘制"]
H["用户文本"] --> I["按输出上下文编码"]
I --> A按顺序看,每一步做了什么
- 先用标题、段落、链接和表单控件表达真实语义。
- 在 DOM 中确认节点关系,再查看匹配的 CSS 声明。
- 逐层比较来源、重要性、层、特异性和适用的排序因素。
- 结合盒模型计算宽度,并在窄窗口与键盘操作下验证结果。
03对照实现代码
OpenJDK 17u,jdk-17.0.16+8 · javax.swing.text.html.HTMLWriter#text、output(char[], int, int)
已核验真实方法及 replaceEntities 控制路径。下例是原创、仅覆盖 HTML 文本中 <、>、& 的局部教学实现,未复制完整源码,不支持属性、脚本或 URL 上下文;Swing 的旧 HTML 支持不能代表现代浏览器全部规范。
class SourceModel {
static String text(String raw) {
if (raw == null) throw new IllegalArgumentException("文本不能为空引用");
StringBuilder out = new StringBuilder();
for (int i=0; i<raw.length(); i++) {
char ch=raw.charAt(i); // 原始数据只扫描一次,避免重复处理新实体
if (ch=='&') out.append("&");
else if (ch=='<') out.append("<");
else if (ch=='>') out.append(">");
else out.append(ch); // 其他字符保持原有顺序
}
return out.toString(); // 结果只适合本例声明的文本上下文
}
}HTMLWriter.text 在输出文档文本时切换实体替换状态,底层 output 根据该状态决定直接写出还是替换特殊字符。控制状态提醒我们:相同字符在标签语法和文本数据中的职责不同,编码必须知道当前输出位置,而不是在整个文档完成后统一替换。
真实 output 会批量写出不需要替换的连续片段,在遇到特殊字符时输出实体。教学模型改为逐字符追加,更容易展示不变量:已处理前缀始终对应等价文本,不会把输入中的小于号误当标签起点。它追求说明机制,不承诺相同的性能特征。
直接扫描原输入,可以避免替换顺序导致二次编码:先把 < 变成实体,再把新结果里所有 & 替换,可能连刚生成的实体也改掉。原用户文本本来含实体字样时,又可能需要把它作为普通文字保留。关键是提前分清收到的是原始文字还是已经编码过的内容,不要混着处理。
这个源码例子发生在 Java 输出端。浏览器如何构树、选择样式和布局要继续查 HTML 与 CSS 规范,不能由 Swing 类名推断。阅读跨语言系统时,明确每段源码位于生产者、协议还是消费者,有助于避免把局部实现误当整个系统的机制。
代码先调用谁,再调用谁
- HTMLWriter 遍历文档元素。
- text 标记当前内容需要实体替换。
- output 区分直接片段与特殊字符。
- Writer 收到序列化文本,浏览器随后按自己的规范处理。
04动手写一个例子
受限 CSS 候选声明的胜出者
输入同一元素、同一属性、同一来源与层中的若干声明,比较 important、特异性三元组和源码顺序,返回胜出值。模型不包含层顺序、内联样式、作用域接近度、动画和过渡;这些因素不能被本算法的数值排序替代。
- 输入 1
普通 ID 红色;普通类蓝色- 输出 1
红色
- 输入 2
普通 ID 红色;important 元素蓝色- 输出 2
蓝色
- 输入 3
相同权重,源码顺序 1 红色与 2 绿色- 输出 3
绿色
- 输入 4
空列表- 输出 4
null,表示没有候选声明
把特异性表示为 ID、类等、元素三个分量,按字典序比较,而不是使用一百分、一十分的加法。这样一个 ID 不会错误地被十一个类选择器超过。
只保留当前最优声明。读取新声明时先比较重要性,再逐分量比较特异性,最后比较源码顺序;只有前面维度相等才看后面。输入顺序号要求非负且表达既定文档次序。
循环结束时当前候选不弱于全部已读声明。main 用 ID 与大量类、important、同权重后声明、空列表及非法负数等自测验证比较器;算法只验证这道受限问题,不替代浏览器的完整层叠。
为什么这样写是对的处理前 i 条声明后,best 是这些声明在本题比较关系下的最大值;没有候选时保持 null。
每条声明只与当前最优项做常数次整数比较;除输入与返回引用外不建立额外集合。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.List;
public class Main {
record Decl(String value, boolean important, int ids, int classes, int tags, int order) {
Decl { // 拒绝不符合本题权重约束的输入
if (value==null || ids<0 || classes<0 || tags<0 || order<0)
throw new IllegalArgumentException();
}
}
static int compare(Decl a, Decl b) {
int c=Boolean.compare(a.important,b.important); // 先比较更高排序维度
if(c!=0) return c;
c=Integer.compare(a.ids,b.ids); if(c!=0) return c;
c=Integer.compare(a.classes,b.classes); if(c!=0) return c;
c=Integer.compare(a.tags,b.tags); if(c!=0) return c;
return Integer.compare(a.order,b.order); // 权重相同时才看源码顺序
}
static String winner(List<Decl> list) {
if(list==null) throw new IllegalArgumentException();
Decl best=null;
for(Decl next:list) {
if(next==null) throw new IllegalArgumentException();
if(best==null || compare(next,best)>=0) best=next;
}
return best==null ? null : best.value;
}
static Decl d(String value,boolean imp,int id,int cls,int tag,int order) {
return new Decl(value,imp,id,cls,tag,order);
}
static void eq(String expected,String actual) {
if(!java.util.Objects.equals(expected,actual)) throw new AssertionError(actual);
}
public static void main(String[] args) {
eq("red",winner(List.of(d("red",false,1,0,0,1),d("blue",false,0,11,0,2))));
eq("blue",winner(List.of(d("red",false,1,0,0,1),d("blue",true,0,0,1,2))));
eq("green",winner(List.of(d("red",false,0,1,0,1),d("green",false,0,1,0,2))));
eq(null,winner(List.of()));
eq("black",winner(List.of(d("black",false,0,0,0,0))));
try { d("x",false,-1,0,0,0); throw new AssertionError("应拒绝负权重"); }
catch(IllegalArgumentException expected) { /* 输入校验通过 */ }
System.out.println("PASS 6 cascade tests");
}
}这些特殊情况也要试一试
- 空候选需要进入继承或初始值流程,不能随意当成空字符串。
- 特异性分量很大时仍使用字典序,不用可能碰撞或溢出的加权总分。
- 本题比较器只适用于已经统一来源和层等前提的候选。
进阶选读 · 深入拓展:完整层叠与增量样式重算
场景大量节点共享样式,某个类名变化只应影响相关候选和后代。
做法先按规范补齐来源、重要性、层及其他排序维度,再建立选择器依赖索引,跟踪哪些节点与继承属性受变化影响。不能把全站重新排序称为浏览器实际算法。
复杂度朴素对 V 个节点与 R 条规则逐一匹配可能达到 O(VR);索引和失效范围缩小常见工作量,但复杂选择器与变化模式影响实际成本。
好处与代价缓存提高重复计算效率,却需要准确失效;漏掉依赖会得到过时样式,过度失效则损失性能。
05项目里怎么用
后端返回的页面和前端组件都应优先复用成熟模板与框架的上下文编码,避免手写函数被用于没有覆盖的场景。允许富文本时,需要明确标签和属性政策,并采用适合该用途的处理方式;纯文本字段没有必要为了方便展示而开放 HTML 解释。
调 CSS 时先看开发者工具里的覆盖关系、计算值和盒模型,找到到底是哪条规则起作用。不断加 !important,可能让以后的修改更难。用组件范围、有限命名和统一样式变量减少全局冲突,通常比把选择器越写越长容易维护。
页面验证还包括语义和可操作性。使用真实 button 与 a,输入关联 label,错误信息不要只靠颜色表达;在键盘、窄窗口和文本放大条件下检查。视觉效果正确并不自动说明接口安全或辅助技术能理解页面,需要分别建立验收条件。
容易踩的坑
- 用样式模拟标题和按钮,可能遗漏语义和键盘行为。
- 认为所有 CSS 都是后写覆盖先写,忽略更高排序维度。
- 把 HTML 文本转义函数直接用于脚本、CSS 或任意 URL 上下文。
- 混用已编码和未编码数据,使双重编码与漏编码交替出现。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 语义 HTML 加少量 CSS | 基础行为明确,学习与调试成本低。 | 复杂交互需要继续设计。 | 表单、列表和内容页面的起点。 |
| 组件化样式 | 减少全局覆盖,利于复用。 | 仍需理解层叠和组件对外样式边界。 | 页面数量与团队协作增加后。 |
| 允许受控富文本 | 支持更丰富内容表达。 | 增加输入政策、输出处理与安全测试成本。 | 确有排版需求且能够维护处理规则的内容系统。 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01HTML 元素与 CSS 外观为什么不能互相替代?
展开参考答案与推导
元素表达结构、语义和默认交互,CSS 主要控制呈现。看起来像按钮的普通块元素未必有焦点、键盘和提交行为。
02为何一个 ID 不会被很多类选择器简单相加超过?
展开参考答案与推导
特异性按元组分量逐级比较,先比较 ID 分量,只有相等才比较后续分量,不是把权重任意换算成一个十进制分数。
03文本转义是否等于对所有 XSS 场景都安全?
展开参考答案与推导
不是。不同输出上下文有不同语法边界,URL 还需要协议等校验。必须明确数据进入的位置,使用对应框架机制。
换个情况,还会处理吗
01卡片指定 300px 却溢出容器,如何排查?
展开参考答案与推导
查看 box-sizing、左右 padding、border 和 margin,再核对父容器可用宽度。先计算盒模型,不把溢出直接归因于浏览器兼容问题。
02更晚写的类规则没覆盖旧 ID 规则,怎么办?
展开参考答案与推导
先确认二者来源、层和 important 等条件,再查看特异性。修正组件样式关系,不应不加分析地继续追加 important。
03商品名含小于号后页面结构异常,定位哪里?
展开参考答案与推导
检查商品名是否被当 HTML 拼入或赋给 innerHTML。纯文本应使用模板的文本编码或 textContent,不能在业务数据中随意删除字符来掩盖输出边界问题。
写代码试一试
01计算 content-box 卡片边框盒宽度。
- 输入 / 条件
width=300,左右 padding=20,左右 border=1- 输出 / 预期
342px
提示 外边距不属于边框盒。
展开参考答案与推导
将内容宽度加两侧内边距和边框。若采用 border-box,声明的 300px 已包含这些部分,内容宽度变为 258px。
02如何测试文本编码不会把输入当标签?
- 输入 / 条件
A<B&C- 输出 / 预期
A<B&C
提示 扫描原始文本。
展开参考答案与推导
逐字符编码语法敏感字符并保持顺序,另测空串与已经包含实体文字的输入,避免对新生成的实体再次扫描。
03给候选比较器增加完整层概念时应注意什么?
- 输入 / 条件
不同层中的普通与 important 声明- 输出 / 预期
必须按规范分开比较,不简单给 layer 加同向分数
提示 层次与重要声明的排序关系不能凭直觉推断。
展开参考答案与推导
先阅读层叠层的规范规则,建立每一维的明确比较表及反例,再实现比较器;本篇受限算法没有覆盖该维度。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能保存并打开一个完整本地页面,用开发者工具检查结构和样式。
本篇重点加练
重点加强页面语义与键盘可访问性,检验看得见的控件是否也能够被正确操作。
- 语义标题链接按钮
- label 与键盘访问
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01设计评审
为个人学习日志设计单页 HTML,包含主标题、日期、阅读链接和提交按钮;标出元素语义、属性职责,以及中文编码和移动端视口配置。
- 提交内容
- HTML 文件与结构说明。
- 怎样算完成
- 标题层级和操作语义可辨
- 基础文档配置完整
练到的知识点HTML 元素与属性 · 语义标题链接按钮 · UTF-8 和 viewport
- 作业 02代码推演
卡片 width=280px,padding=16px,border=2px;分别使用 content-box 与 border-box,先推导占用宽度再用浏览器检查。
- 提交内容
- 两份样式、尺寸计算及测量记录。
- 怎样算完成
- 盒模型各部分分别计量
- 不只凭视觉估计
练到的知识点CSS 选择器 · 盒模型和 border-box
综合应用
- 作业 03排错实验
同一段日志文字同时匹配元素、类与 ID 选择器,再加入行内样式;每次只改一个声明,记录最终计算样式与规则来源。
- 提交内容
- 最小页面、自写样式检查表与浏览器证据。
- 怎样算完成
- 选择器与声明来源可追踪
- 结论适用于实际实验条件
练到的知识点层叠和计算样式
- 作业 04编程实践
完成一个包含标题输入、正文输入和保存按钮的静态表单,确保 label 可定位输入项;只用键盘遍历全部控件,并在 320px 宽度检查长链接和长中文段落。
- 提交内容
- HTML
/CSS、自写键盘与窄屏验收表、浏览器运行证据。 - 怎样算完成
- 每个输入有可识别标签
- 焦点可见且顺序可用
- 无无法访问的横向溢出内容
练到的知识点label 与键盘访问 · 窄屏与溢出检查
- 作业 05面试场景
设计稿把一个 div 画成可点击“返回目录”。评审链接、按钮和普通容器三种实现的键盘行为、语义和后续脚本职责。
- 提交内容
- 交互合同与实现比较。
- 怎样算完成
- 按真实操作选择元素
- 无鼠标使用路径完整
练到的知识点HTML 元素与属性 · 语义标题链接按钮 · label 与键盘访问
- 作业 06编程实践
制作纯 HTML 学习目录,含站内链接、下载链接、普通按钮与禁用按钮;只用键盘执行可执行项,记录焦点和触发行为,不添加
JavaScript。- 提交内容
- HTML 文件、自写键盘检查表和浏览器记录。
- 怎样算完成
- 元素选择对应具体动作
- 所有可操作项有可识别名称
练到的知识点语义标题链接按钮 · label 与键盘访问
- 作业 07设计评审
设计稿用同一图标表示“打开文章”“展开说明”和“提交表单”。分别规定动作语义和可见/辅助名称,说明何时不应套用相同 HTML 元素。
- 提交内容
- 三项交互合同与元素草图。
- 怎样算完成
- 视觉相同不代替操作语义
- 名称能说明动作目标
练到的知识点语义标题链接按钮
- 作业 08排错实验
为两个输入框设置 label,故意引入一次重复 id 和一次关联目标拼写错误;观察点击标签和键盘操作,再修复并复测。
- 提交内容
- 最小页面、自写关联验收及前后证据。
- 怎样算完成
- 每个标签目标可定位
- 修复覆盖所有控件而非单个截图
练到的知识点label 与键盘访问
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
完成响应式布局后:日志侧栏在大屏固定宽度、小屏收起,正文含长代码。制定宽度、换行、横向滚动与缩放到 200% 的验收标准。
- 提交内容
- 布局规则与多尺寸检查矩阵。
- 怎样算完成
- 阅读内容不被不可恢复截断
- 滚动区域与键盘可访问性明确
练到的知识点CSS 选择器 · 盒模型和 border-box · 层叠和计算样式 · 窄屏与溢出检查
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
页面从鼠标使用改为完全无鼠标操作,且图标字体加载失败。审查哪些动作仍能被发现和执行,为现有目录提出最少调整。
- 提交内容
- 失效条件下的操作清单与改动理由。
- 怎样算完成
- 核心功能无需猜图标含义
- 验收由实际操作路径支持
练到的知识点语义标题链接按钮 · label 与键盘访问
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读教程中文
MDN:HTML 结构基础 (新标签页)↗
学习语义元素、标题、链接与表单结构,先让页面可读且可操作。
- 实践教程中文
MDN:CSS 样式基础 (新标签页)↗
阅读选择器、层叠、盒模型和布局起点,用开发者工具检查计算样式。
- 深入规范英文
WHATWG HTML:表单 (新标签页)↗
查阅 label、控件 name、提交方法与 enctype,定位浏览器到底发送哪些字段。
- 深入参考文档英文
WHATWG HTML (新标签页)↗
阅读文档结构、解析和元素语义,区分源文本与 DOM。
- 深入参考文档英文
CSS Cascading and Inheritance Level 5 (新标签页)↗
阅读层叠排序与层的规则,核对受限算法的适用边界。
- 深入参考文档英文
OpenJDK 17u HTMLWriter (新标签页)↗
定位 text 的状态切换及 output 的实体处理,理解 Java 输出端职责。