无序列表用于标记列表项目顺序无关紧要的列表——让我们以购物清单为例。
豆浆
油条
豆汁
焦圈
每份无序的清单从 <ul>
元素开始,需要包裹清单上所有被列出的项目:
然后就是用 <li>
(list item,列表项)元素把每个列出的项目单独包裹起来:
<ul>
<li>豆浆</li>
<li>油条</li>
<li>豆汁</li>
<li>焦圈</li>
</ul>
尝试编辑下面的运行实例来创建一个 HTML 无序列表。
<h2>实时输出</h2>
<div class="output" style="min-height: 50px;"></div>
<h2>可编辑代码</h2>
<p class="a11y-label">
按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code>
</p>
<textarea id="code" class="input" style="min-height: 100px; width: 95%">
豆浆
油条
豆汁
焦圈
</textarea>
<div class="playable-buttons">
<input id="reset" type="button" value="重置" />
<input id="solution" type="button" value="显示答案" />
</div>
html {
font-family: sans-serif;
}
h2 {
font-size: 16px;
}
.a11y-label {
margin: 0;
text-align: right;
font-size: 0.7rem;
width: 98%;
}
body {
margin: 10px;
background: #f5f9fa;
}
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
const output = document.querySelector(".output");
const code = textarea.value;
let userEntry = textarea.value;
function updateCode() {
output.innerHTML = textarea.value;
}
const htmlSolution =
"<ul>\n<li>豆浆</li>\n<li>油条</li>\n<li>豆汁</li>\n<li>焦圈</li>\n</ul>";
let solutionEntry = htmlSolution;
reset.addEventListener("click", () => {
textarea.value = code;
userEntry = textarea.value;
solutionEntry = htmlSolution;
solution.value = "显示答案";
updateCode();
});
solution.addEventListener("click", () => {
if (solution.value === "显示答案") {
textarea.value = solutionEntry;
solution.value = "隐藏答案";
} else {
textarea.value = userEntry;
solution.value = "显示答案";
}
updateCode();
});
textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
// 阻止制表键跳出文本区域
// 而是在光标位置输出制表符
textarea.onkeydown = (e) => {
if (e.code === "Tab") {
e.preventDefault();
insertAtCaret("\t");
}
if (e.code === "Escape") {
textarea.blur();
}
};
function insertAtCaret(text) {
const scrollPos = textarea.scrollTop;
let caretPos = textarea.selectionStart;
const front = textarea.value.substring(0, caretPos);
const back = textarea.value.substring(
textarea.selectionEnd,
textarea.value.length,
);
textarea.value = front + text + back;
caretPos += text.length;
textarea.selectionStart = caretPos;
textarea.selectionEnd = caretPos;
textarea.focus();
textarea.scrollTop = scrollPos;
}
// 每次用户更新文本区域代码时,更新已保存的 userCode
textarea.onkeyup = () => {
// 我们只想在显示用户代码时保存状态,
// 而不是保存解答,因此解答不会保存在用户代码上
if (solution.value === "显示答案") {
userEntry = textarea.value;
} else {
solutionEntry = textarea.value;
}
updateCode();
};
有序列表需要按照项目的顺序列出来——让我们以一组方向指示为例:
沿这条路走到头
右转
直行穿过第一个十字路口
在第三个十字路口处左转
继续走 300 米,学校就在你的右手边
这个标记的结构和无序列表一样,除了需要用 <ol>
元素而不是 <ul>
元素将所有项目包裹:
<ol>
<li>沿这条路走到头</li>
<li>右转</li>
<li>直行穿过第一个十字路口</li>
<li>在第三个十字路口处左转</li>
<li>继续走 300 米,学校就在你的右手边</li>
</ol>
尝试编辑下面的示例来创建一个 HTML 有序列表:
<h2>实时输出</h2>
<div class="output" style="min-height: 50px;"></div>
<h2>可编辑代码</h2>
<p class="a11y-label">
按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code>
</p>
<textarea id="code" class="input" style="min-height: 200px; width: 95%">
沿这条路走到头
右转
直行穿过第一个十字路口
在第三个十字路口处左转
继续走 300 米,学校就在你的右手边
</textarea>
<div class="playable-buttons">
<input id="reset" type="button" value="重置" />
<input id="solution" type="button" value="显示答案" />
</div>
html {
font-family: sans-serif;
}
h2 {
font-size: 16px;
}
.a11y-label {
margin: 0;
text-align: right;
font-size: 0.7rem;
width: 98%;
}
body {
margin: 10px;
background: #f5f9fa;
}
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
const output = document.querySelector(".output");
const code = textarea.value;
let userEntry = textarea.value;
function updateCode() {
output.innerHTML = textarea.value;
}
const htmlSolution = `<ol>
<li>沿这条路走到头</li>
<li>右转</li>
<li>直行穿过第一个十字路口</li>
<li>在第三个十字路口处左转</li>
<li>继续走 300 米,学校就在你的右手边</li>
</ol>`;
let solutionEntry = htmlSolution;
reset.addEventListener("click", () => {
textarea.value = code;
userEntry = textarea.value;
solutionEntry = htmlSolution;
solution.value = "显示答案";
updateCode();
});
solution.addEventListener("click", () => {
if (solution.value === "显示答案") {
textarea.value = solutionEntry;
solution.value = "隐藏答案";
} else {
textarea.value = userEntry;
solution.value = "显示答案";
}
updateCode();
});
textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
// 阻止制表键跳出文本区域
// 而是在光标位置输出制表符
textarea.onkeydown = (e) => {
if (e.code === "Tab") {
e.preventDefault();
insertAtCaret("\t");
}
if (e.code === "Escape") {
textarea.blur();
}
};
function insertAtCaret(text) {
const scrollPos = textarea.scrollTop;
let caretPos = textarea.selectionStart;
const front = textarea.value.substring(0, caretPos);
const back = textarea.value.substring(
textarea.selectionEnd,
textarea.value.length,
);
textarea.value = front + text + back;
caretPos += text.length;
textarea.selectionStart = caretPos;
textarea.selectionEnd = caretPos;
textarea.focus();
textarea.scrollTop = scrollPos;
}
// 每次用户更新文本区域代码时,更新已保存的 userCode
textarea.onkeyup = () => {
// 我们只想在显示用户代码时保存状态,
// 而不是保存解答,因此解答不会保存在用户代码上
if (solution.value === "显示答案") {
userEntry = textarea.value;
} else {
solutionEntry = textarea.value;
}
updateCode();
};
到了这里,你拥有了所有你需要的信息来标记我们的食谱样例。你可以选择创建一份 text-start.html 起始文件的本地拷贝并在本地完成它,或者在下面的可编辑示例中进行。在本地做可能会更好,因为这样你就可以保存你正在做的工作,而如果你把它填到可编辑的例子中,在你下次打开页面时,它就会丢失。各有利弊吧。
<h2>实时输出</h2>
<div class="output" style="min-height: 50px;"></div>
<h2>可编辑代码</h2>
<p class="a11y-label">
按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code>
</p>
<textarea id="code" class="input" style="min-height: 200px; width: 95%">
宫保鸡丁的做法
宫保鸡丁,川菜系中的传统名菜,由鸡丁、干辣椒、花生米等炒制而成。由于其入口鲜辣,鸡肉的鲜嫩配合花生的香脆,广受大众欢迎。
相传宫保鸡丁是清朝光绪年间的署理四川总督丁宝桢所发明,是他招待客人时叫家厨煮的菜肴。由于丁宝桢后来被封为东宫少保(太子少保),所以被称为“丁宫保”,而这道菜亦被称为“宫保鸡丁”
原料
去骨鸡胸肉:一斤八两
干红辣椒:八钱
炸花生米:一两五钱
花椒粒:两大匙
葱:两根(切段)
蛋白:一个
淀粉:三大匙
酱油:两大匙
蒜末:半茶匙
糖:半茶匙
白醋:一茶匙
色拉油:适量
盐:两茶匙
做法
先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。
用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。
鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。
在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。
如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。
如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。
大千鸡
张大千居加拿大期间,曾按自己喜好改变宫保鸡丁的做法,并传授当地厨师,厨师将之命名为“大千鸡”,以兹纪念。大千鸡与宫保鸡丁不同之处,是使用经细工去皮、出骨、剔膜的鸡腿肉,以干辣椒、豆瓣酱为味,而且不用花生。</textarea
>
<div class="playable-buttons">
<input id="reset" type="button" value="重置" />
<input id="solution" type="button" value="显示答案" />
</div>
html {
font-family: sans-serif;
}
h2 {
font-size: 16px;
}
.a11y-label {
margin: 0;
text-align: right;
font-size: 0.7rem;
width: 98%;
}
body {
margin: 10px;
background: #f5f9fa;
}
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
const output = document.querySelector(".output");
const code = textarea.value;
let userEntry = textarea.value;
function updateCode() {
output.innerHTML = textarea.value;
}
const htmlSolution = `<h1>宫保鸡丁的做法</h1>
<p>宫保鸡丁,川菜系中的传统名菜,由鸡丁、干辣椒、花生米等炒制而成。由于其入口鲜辣,鸡肉的鲜嫩配合花生的香脆,广受大众欢迎。</p>
<p>相传宫保鸡丁是清朝光绪年间的署理四川总督丁宝桢所发明,是他招待客人时叫家厨煮的菜肴。由于丁宝桢后来被封为东宫少保(太子少保),所以被称为“丁宫保”,而这道菜亦被称为“宫保鸡丁”</p>
<h2>原料</h2>
<ul>
<li>去骨鸡胸肉:一斤八两</li>
<li>干红辣椒:八钱</li>
<li>炸花生米:一两五钱</li>
<li>花椒粒:两大匙</li>
<li>葱:两根(切段)</li>
<li>蛋白:一个</li>
<li>淀粉:三大匙</li>
<li>酱油:两大匙</li>
<li>蒜末:半茶匙</li>
<li>糖:半茶匙</li>
<li>白醋:一茶匙</li>
<li>色拉油:适量</li>
<li>盐:两茶匙</li>
</ul>
<h2>做法</h2>
<ol>
<li>先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。</li>
<li>用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。</li>
<li>鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。</li>
<li>在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。</li>
<li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li>
<li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li>
</ol>
<h2>大千鸡</h2>
<p>张大千居加拿大期间,曾按自己喜好改变宫保鸡丁的做法,并传授当地厨师,厨师将之命名为“大千鸡”,以兹纪念。大千鸡与宫保鸡丁不同之处,是使用经细工去皮、出骨、剔膜的鸡腿肉,以干辣椒、豆瓣酱为味,而且不用花生。</p>`;
let solutionEntry = htmlSolution;
reset.addEventListener("click", () => {
textarea.value = code;
userEntry = textarea.value;
solutionEntry = htmlSolution;
solution.value = "显示答案";
updateCode();
});
solution.addEventListener("click", () => {
if (solution.value === "显示答案") {
textarea.value = solutionEntry;
solution.value = "隐藏答案";
} else {
textarea.value = userEntry;
solution.value = "显示答案";
}
updateCode();
});
textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
// 阻止制表键跳出文本区域
// 而是在光标位置输出制表符
textarea.onkeydown = (e) => {
if (e.code === "Tab") {
e.preventDefault();
insertAtCaret("\t");
}
if (e.code === "Escape") {
textarea.blur();
}
};
function insertAtCaret(text) {
const scrollPos = textarea.scrollTop;
let caretPos = textarea.selectionStart;
const front = textarea.value.substring(0, caretPos);
const back = textarea.value.substring(
textarea.selectionEnd,
textarea.value.length,
);
textarea.value = front + text + back;
caretPos += text.length;
textarea.selectionStart = caretPos;
textarea.selectionEnd = caretPos;
textarea.focus();
textarea.scrollTop = scrollPos;
}
// 每次用户更新文本区域代码时,更新已保存的 userCode
textarea.onkeyup = () => {
// 我们只想在显示用户代码时保存状态,
// 而不是保存解答,因此解答不会保存在用户代码上
if (solution.value === "显示答案") {
userEntry = textarea.value;
} else {
solutionEntry = textarea.value;
}
updateCode();
};
如果你感到棘手,你可以随时按下显示答案按钮,或者在我们的 GitHub 仓库上检查 text-complete.html 样例。
将一个列表嵌入到另一个列表是完全可以的。你可能想让一些子项目列在一级项目之下。让我们从食谱示例中获取第二个列表:
<ol>
<li>
先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。
</li>
<li>
用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。
</li>
<li>
鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。
</li>
<li>
在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。
</li>
<li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li>
<li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li>
</ol>
由于最后两项与它们的前一项非常密切相关(它们看起来更像该项的子项或选项),将它们编辑成无序列表,并嵌套在该项的子项中可能更合理。就像下面这样:
<ol>
<li>
先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。
</li>
<li>
用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。
</li>
<li>
鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。
</li>
<li>
在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。
<ul>
<li>
如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。
</li>
<li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li>
</ul>
</li>
</ol>
尝试回到上一个实践操作的例子中,并更新第二个列表。
描述列表的目的是标记一组项目及其相关描述,如术语和定义或问题和答案。让我们来看一组术语和定义的示例:
内心独白
戏剧中,某个角色对自己的内心活动或感受进行念白表演,这些台词只面向观众,而其他角色不会听到。
语言独白
戏剧中,某个角色把自己的想法直接进行念白表演,观众和其他角色都可以听到。
旁白
戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。
描述列表使用与其他列表类型不同的包裹标签——<dl>
;此外,每一项都用 <dt>
(description term,描述术语)元素包裹。每个描述都用 <dd>
(description definition,描述定义)元素包裹。
让我们来完成下面的标记例子:
<dl>
<dt>内心独白</dt>
<dd>
戏剧中,某个角色对自己的内心活动或感受进行念白表演,这些台词只面向观众,而其他角色不会听到。
</dd>
<dt>语言独白</dt>
<dd>
戏剧中,某个角色把自己的想法直接进行念白表演,观众和其他角色都可以听到。
</dd>
<dt>旁白</dt>
<dd>
戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。
</dd>
</dl>
浏览器的默认样式会在描述列表的术语及其定义之间产生缩进。
请注意,一个术语可以同时有多个描述,例如:
<dl>
<dt>旁白</dt>
<dd>
戏剧中,为渲染幽默或戏剧性效果而进行的场景之外的补充注释念白,只面向观众,内容一般都是角色的感受、想法、以及一些背景信息等。
</dd>
<dd>
写作中,指与当前主题相关的一段内容,通常不适于直接置于内容主线中,因此置于附近的其他位置(通常位于主线内容旁边一个文本框内)。
</dd>
</dl>
现在是时候尝试一下描述列表了。在输入区域的原始文本里添加相应的元素,使得它在输出区域是以描述列表的形式出现。如果你喜欢,你也可以使用你自己的描述术语和描述。
如果你做错了,你可以随时点击重置按钮。如果实在进行不下去,可以点击显示答案。
<h2>实时输出</h2>
<div class="output" style="min-height: 50px;"></div>
<h2>可编辑代码</h2>
<p class="a11y-label">
按 ESC 退出编辑区域,按 Tab 可插入制表符 <code>'\t'</code>
</p>
<textarea id="code" class="input" style="min-height: 100px; width: 95%">
培根
整个世界的粘合剂。
鸡蛋
一块蛋糕的粘合剂。
咖啡
一种浅棕色的饮料。
可以在清晨带来活力。
</textarea>
<div class="playable-buttons">
<input id="reset" type="button" value="重置" />
<input id="solution" type="button" value="显示答案" />
</div>
html {
font-family: sans-serif;
}
h2 {
font-size: 16px;
}
.a11y-label {
margin: 0;
text-align: right;
font-size: 0.7rem;
width: 98%;
}
body {
margin: 10px;
background: #f5f9fa;
}
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
const output = document.querySelector(".output");
const code = textarea.value;
let userEntry = textarea.value;
function updateCode() {
output.innerHTML = textarea.value;
}
const htmlSolution =
"<dl>\n <dt>培根</dt>\n <dd>整个世界的粘合剂。</dd>\n <dt>鸡蛋</dt>\n <dd>一块蛋糕的粘合剂。</dd>\n <dt>咖啡</dt>\n <dd>一种浅棕色的饮料。</dd>\n <dd>可以在清晨带来活力。</dd>\n </dl>";
let solutionEntry = htmlSolution;
reset.addEventListener("click", () => {
textarea.value = code;
userEntry = textarea.value;
solutionEntry = htmlSolution;
solution.value = "显示答案";
updateCode();
});
solution.addEventListener("click", () => {
if (solution.value === "显示答案") {
textarea.value = solutionEntry;
solution.value = "隐藏答案";
} else {
textarea.value = userEntry;
solution.value = "显示答案";
}
updateCode();
});
textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
// 阻止制表键跳出文本区域
// 而是在光标位置输出制表符
textarea.onkeydown = (e) => {
if (e.code === "Tab") {
e.preventDefault();
insertAtCaret("\t");
}
if (e.code === "Escape") {
textarea.blur();
}
};
function insertAtCaret(text) {
const scrollPos = textarea.scrollTop;
let caretPos = textarea.selectionStart;
const front = textarea.value.substring(0, caretPos);
const back = textarea.value.substring(
textarea.selectionEnd,
textarea.value.length,
);
textarea.value = front + text + back;
caretPos += text.length;
textarea.selectionStart = caretPos;
textarea.selectionEnd = caretPos;
textarea.focus();
textarea.scrollTop = scrollPos;
}
// 每次用户更新文本区域代码时,更新已保存的 userCode
textarea.onkeyup = () => {
// 我们只想在显示用户代码时保存状态,
// 而不是保存解答,因此解答不会保存在用户代码上
if (solution.value === "显示答案") {
userEntry = textarea.value;
} else {
solutionEntry = textarea.value;
}
updateCode();
};
关于 HTML 基本语义元素的三篇文章已经读完,但你还记得最重要的信息吗?你可以找到一些进一步的测试,以验证你在继续前进之前已经保留了这些信息——参见技能测试:HTML 文本基础知识。
列表就到此为止。接下来我们将进行更高层次的讨论。我们已经展示了如何实现一些单个页面的功能,但如何构建整个 HTML 页面呢?接下来将讨论文档结构。