Day 21 | 页内锚点:给每段起个 id,目录写 href="#id",点一下页面就滚到那一段
本文是《100天精通HTML基础入门》专栏第 21 篇。昨天刚把"把用户送出门"这件事上了锁,今天调头回站内------不跳页、不开窗,就在同一份文档里上下来回走。
摘要 :本文基于 HTML5 现行规范与浏览器对 URL 片段标识(#id)的处理,实测环境 Chrome 128。锚点是两半的活:目标元素挂一个id,目录链接写href="#同一个id",点一下页面就滚到那一段。我把两份只差一个字母的代码并排跑了一遍:拼错那份的表现是跳不动、不报错、地址栏也不变,典型的无声失败。这套片段定位机制多年没变,学了长期适用;<a name="...">是 HTML 4 的已废弃老写法,看到别照抄。适合刚把target、rel理顺、正要动手写长文页的你。
本文目录
-
先摆一组对照:同一份目录,一份跳得动,一份原地装死
-
锚点是两半的活:id 是门牌,href="#id" 是那张指路便签
-
逐行拆差异:s 和 i 换了个位,页面一点动静都没有
-
为什么它不报错:浏览器拿到 # 那串只做一件事
-
今日产出:一篇带目录、五段 id 和回顶链接的长文页
-
小结:回头看锚点这三条硬规矩
-
动手改一改:名片页上三个必改项
-
明天见(Day 22):今晚先把这篇文章切成几段想好

先摆一组对照:同一份目录,一份跳得动,一份原地装死
先说最要命的那件:页内锚点这件事,写对了和写错了,浏览器给你的反馈差别是------对的会滚,错的什么都不给。第一回碰上这种"点了三下没动静",谁不愣一下呢。
还记得 Day 19 那片"嵌不进就安静留白"吗?当时那句结论我至今贴在显示器边上:浏览器遇到安全和边界类的情况,只低声处理,不敲锣打鼓。锚点继承的是同一副脾气。Day 18 那个"漏了 https:// 被当成本地路径"也是这路子------它不喊,它照自己的规矩办事。今天这个更狠:连状态栏都不变一下。
我先把两份代码并排放这儿,你一眼就能看见"差一个字母"值多少反应:
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页内锚点 · 改前 / 改后对照</title>
<style>
/* 左右两栏各是一份小目录加正文,唯一的差别是 id 拼写对不对得上 href */
/* 样式不用细看,理解为给网页化妆即可,CSS 专栏里会展开 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #f7f5ef;
color: #24292f; margin: 0; line-height: 1.75; }
.wrap { max-width: 760px; margin: 24px auto; background: #fff;
border: 1px solid #d8d3c8; border-radius: 14px; padding: 22px 26px; }
h1 { font-size: 22px; margin: 0 0 14px; }
.cols { display: flex; gap: 18px; flex-wrap: wrap; }
.col { flex: 1; min-width: 300px; border: 1px solid #d8d3c8;
border-radius: 10px; padding: 14px 16px; }
.bad { border-color: #b0342a; }
.good { border-color: #2f4a22; }
.tag { display: inline-block; font-size: 12px; padding: 2px 10px;
border-radius: 999px; color: #fff; margin-bottom: 8px; }
.tag.b { background: #b0342a; }
.tag.g { background: #2f4a22; }
ul { margin: 0 0 10px; padding-left: 20px; }
li { font-size: 14px; }
a { color: #1b4055; }
code { color: #b0342a; }
.blk { height: 92px; border: 1px dashed #c9c3b6; border-radius: 8px;
padding: 8px 10px; margin-top: 8px; font-size: 13px; background: #fbfaf6; }
.foot { font-size: 13px; color: #6b7280; margin-top: 14px; }
</style>
</head>
<body>
<div class="wrap">
<h1>同一份目录,两个结果:点"第三段"会发生什么</h1>
<div class="cols">
<div class="col bad">
<p><span class="tag b">改前</span></p>
<ul>
<li><a href="#sec-one">第一段</a></li>
<li><a href="#sec-two">第二段</a></li>
<li><a href="#sectoin">第三段(这行拼错了)</a></li>
</ul>
<div class="blk" id="sec-one">第一段:这页里能跳。</div>
<div class="blk" id="sec-two">第二段:这页里能跳。</div>
<div class="blk">第三段:我的 id 是 <code>section</code>,跟 href 对不上。</div>
</div>
<div class="col good">
<p><span class="tag g">改后</span></p>
<ul>
<li><a href="#sec-one-ok">第一段</a></li>
<li><a href="#sec-two-ok">第二段</a></li>
<li><a href="#sec-three-ok">第三段</a></li>
</ul>
<div class="blk" id="sec-one-ok">第一段:点目录,滚到这。</div>
<div class="blk" id="sec-two-ok">第二段:点目录,滚到这。</div>
<div class="blk" id="sec-three-ok">第三段:id 和 href 一模一样,照样滚过来。</div>
</div>
</div>
<p class="foot">验证方式:双击打开这页,左右各点一次"第三段"。左边地址栏不出现 <code>#sectoin</code>,页面纹丝不动;右边地址栏变成 <code>#sec-three-ok</code>,那块灰底卡片顶到窗口上沿。这就是当天要拿到的成果。</p>
</div>
</body>
</html>


运行结果对照很清楚:右边点一下,卡片"咔"一下顶到窗口上沿,地址栏尾巴多了 #sec-three-ok;左边点三下,页面无风无雨,地址栏一个字没加。对比数据就两处------一个是地址栏里那串 #,一个是滚动位置------两处都没有,基本能定位到"名对不上",不用怀疑自己手抖呀。
锚点是两半的活:id 是门牌,href="#id" 是那张指路便签
先说最要紧的一条:锚点是两个元素之间的事 ,不是一行代码的事。目标那头要有一块 id(门牌号),出发这头的 href 写成 # + 同一个号(便签上写着"去几号")。少任何一半都不叫锚点。
它的完整骨架就这么点东西:
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>锚点最小骨架:一个 id,一个 href="#id"</title>
<style>
/* 最小可跑:两块正文各一个 id,上面两条目录用 # 指过去 */
/* :target 是"你此刻正落在这块上"的意思,只用来给验收加个底色 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #f7f5ef;
color: #24292f; margin: 0; line-height: 1.8; }
.wrap { max-width: 640px; margin: 24px auto; background: #fff;
border: 1px solid #d8d3c8; border-radius: 14px; padding: 22px 26px; }
h1 { font-size: 21px; margin: 0 0 10px; }
nav { background: #fbf7e8; border: 1px solid #e6dcc0; border-radius: 10px;
padding: 10px 14px; margin-bottom: 14px; }
nav p { margin: 0 0 4px; font-size: 13px; color: #6b7280; }
nav ul { margin: 0; padding-left: 20px; }
li { font-size: 14px; margin-bottom: 3px; }
a { color: #1b4055; }
.sec { border: 1px solid #e2ddd2; border-radius: 10px; padding: 14px 16px;
margin-bottom: 12px; }
.sec h2 { font-size: 16px; margin: 0 0 6px; }
.sec p { font-size: 14px; margin: 0 0 8px; }
.sec:target { border-color: #d9a441; background: #fff8e6; }
.spacer { height: 420px; }
.top { font-size: 13px; }
</style>
</head>
<body>
<div class="wrap">
<h1>先跑通最小的一份</h1>
<nav>
<p>下面两条是页内目录,点它会滚到对应那块:</p>
<ul>
<li><a href="#part-1">第一部分 · 为什么要有 id</a></li>
<li><a href="#part-2">第二部分 · href 前面那个 # 是什么</a></li>
</ul>
</nav>
<div class="sec" id="part-1">
<h2>第一部分 · 为什么要有 id</h2>
<p>id 是这张页面里给某个元素起的唯一名字。同一个页面里,它不能重复;写了两个同名的,浏览器只认排在前面的那一个。</p>
<p class="top"><a href="#part-2">去第二部分 →</a></p>
</div>
<div class="spacer"></div>
<div class="sec" id="part-2">
<h2>第二部分 · href 前面那个 # 是什么</h2>
<p><code>href="#part-1"</code> 里的 <code>#</code> 读作"片段",意思是:别去请求新文档,就在当前这份文档里找一个 id 叫 part-1 的元素,把它滚到能看见的地方。</p>
<p class="top"><a href="#part-1">← 回第一部分</a></p>
</div>
</div>
</body>
</html>


现象你应该看到:点目录里第一行,那块卡片边框会变色------那是 :target 在告诉你"你落在它上面了"。这个底色就是最省事的验证信号:落了就有色,没落就说明名没对上。还有个小细节值得注意,地址栏这会儿变成 ...min-anchor.html#part-1,刷新一下页面,它会直接停在第二部分,因为地址里那串 # 本身带着位置信息嘛。
id 该起成什么样,规范里有硬约束,我给你收进一张表:
| 要求 | 合法写法 | 不合法/给自己埋雷 |
|---|---|---|
| 不能有空格 | id="part-2" |
id="part 2"(href 里没法干净地写空格) |
| 不能以数字开头 | id="s2-summary" |
id="2-summary" |
| 至少一个字符 | id="top" |
id=""(空 id 等于没写) |
| 全页唯一 | 每块各一个名字 | 两块都用 id="top" |
| 大小写要和 href 对齐 | #Part-1 对 id="Part-1" |
目标 id="part-1",链接写 href="#Part-1" |

逐行拆差异:s 和 i 换了个位,页面一点动静都没有
上面表格最后两行才是新手真正的重灾区。大纲里给 Day 21 记的坑就是它:id 打错一个字母,跳转无声无息失败。我把这个现场还原给你看,注意它连一行报错都舍不得给你:
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>翻车现场 · id 打错一个字母,点了三下没反应</title>
<style>
/* 仿真:目录点下去毫无反应,地址栏没出现 #,F12 里一看目标 id 拼成了 sectoin */
/* 真实工程里这叫 silent failure(静默失败):不抛异常、不弹红字、不联网 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #1a1c20;
color: #e6e6e6; margin: 0; line-height: 1.7; }
.addr { margin: 12px; background: #3a3f47; color: #9cdcfe;
font-family: Consolas, monospace; font-size: 13px; padding: 9px 12px;
border-radius: 8px; overflow-wrap: anywhere; }
.addr b { color: #ffca6b; }
.body { background: #f7f5ef; color: #24292f; margin: 0 12px 12px;
border-radius: 10px; padding: 16px 20px; }
.body h1 { font-size: 18px; margin: 0 0 8px; }
.body a { color: #1b4055; }
.body ul { margin: 0 0 10px; padding-left: 20px; }
.body li { font-size: 14px; margin-bottom: 4px; }
.blk { border: 1px dashed #c9c3b6; border-radius: 8px; padding: 8px 10px;
margin-top: 6px; font-size: 13px; background: #fbfaf6; }
.dev { margin: 0 12px 14px; background: #25282d; border-radius: 10px;
padding: 12px 16px; font-family: Consolas, monospace; font-size: 12.5px;
color: #d4d4d4; overflow-wrap: anywhere; }
.dev em { color: #6a9955; font-style: normal; }
.dev i { color: #b0342a; font-style: normal; }
.warn { margin: 0 12px 14px; background: #3a2b1c; color: #ffca6b;
border-left: 4px solid #d9a441; border-radius: 0 8px 8px 0;
padding: 10px 14px; font-size: 13px; }
</style>
</head>
<body>
<div class="addr">file:///D:/CSDN%E9%95%BF%E6%96%87/longread.html ------ <b>点了三次"第三段",地址栏一个字没变</b></div>
<div class="body">
<h1>我的长文页(装死版)</h1>
<ul>
<li><a href="#sec-one">第一段</a></li>
<li><a href="#sec-two">第二段</a></li>
<li><a href="#sectoin">第三段 ← 我就点它</a></li>
</ul>
<div class="blk">第一段正文......</div>
<div class="blk">第二段正文......</div>
<div class="blk" id="section">第三段正文:我的 id 少了一个字母的顺序。</div>
</div>
<div class="dev">
<div><em><div class="blk" <i>id="section"</i>></em>第三段正文......</div></div>
<div><em><a <i>href="#sectoin"</i>></em>第三段</a></em></div>
<div><em>// 左边 section,右边 sectoin ------ 差一个字母的位置,浏览器不认为它们是同一个名字</em></div>
</div>
<div class="warn">控制台里没有红字。这一类失败不抛异常,只表现为"点了没反应"------所以只能靠你自己搜,别指望它提示你。</div>
</body>
</html>

现象摆这儿:地址栏没变、页面没滚、控制台干净。为什么?因为浏览器压根没开始"跳"这个动作------它连目标都找不到,自然不会有任何后续行为。哎,这跟 Day 20 那次"裸 _blank 留了个后门"是一个道理:浏览器不会替你检查意图,它只按字面执行你写的字符串。

三步排查:id 到底存不存在
按这个顺序走,别一上来就改代码。
-
点一下目录,先看地址栏有没有冒出
#xxx。没有,说明链接本身可能压根不是锚点写法(比如漏了#)。 -
冒出来了,页面却没滚:把
#后面那串复制下来,按 Ctrl+F 在源码里搜,看它是不是只出现在链接里、而目标元素身上没有。 -
两处都有但差一个字母或大小写不一致:以目标元素的 id 为准改链接,别反过来------id 往往还被别处引用着。
为什么它不报错:浏览器拿到 # 那串只做一件事
原理拆开其实很薄。你点 href="#part-2",浏览器不请求任何新文档,它做的就一件事:拿这段字符串当名字,在当前文档里找一个 id 等于它的元素。
找到了,就把那个元素滚到视口上沿,顺便把 #part-2 记进地址栏;找不到,就什么也不做------它没有"跳去一个不存在的地方"这种中间态可以表达,也就没有可报错的对象。之所以它不像 Day 19 那样至少在控制台留一句 Refused to display,是因为那件事涉及"别人家的服务器回了响应头",而锚点这一趟压根没出浏览器,连一次网络请求都没发出去。
| 你写的 href | 浏览器理解成 | 会不会请求新文档 |
|---|---|---|
href="#part-1" |
当前文档里找 id=part-1 | 不会 |
href="other.html#part-1" |
先打开 other.html,再找 id=part-1 | 会 |
href="other.html" |
打开另一页,停在顶部 | 会 |
href="#" |
只把片段清空,滚回页面顶部 | 不会 |
顺带把边界说清,免得你以为锚点万能。注意三点:一,id 只在这一份文档内 唯一,所以跨页跳转必须写全 另一页.html#id,只写 #id 是找不到对面那页的东西的;二,如果那一段被 display: none 藏起来了,元素找不到可见位置,滚过去也就没有效果;三,前置条件是你得先给目标元素起好名字------目录里那三条链接,背后必须真有对应 id,不然就是今天这个坑。
再多说一句跳不动时的分流,拿这张图对着查比背口诀快:

还有一种更阴的:页面上两个元素用了同一个 id="top"。它不报错,也不"失败",只是永远跳到第一个。你写的是"回到顶部",结果每次都被扔到页面中段的某个卡片上。我改自己那页的时候就是这症状,盯了半天才发现底下藏着第二个同名 id。
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>翻车现场 · 两个 id="top",回顶按钮跳去了半路</title>
<style>
/* 仿真:同名 id 只认第一个,于是"回到顶部"滚到了文章开头的第 2 块 */
/* 这类现象最难查:页面确实滚了,只是滚错地方,不像 bug 像闹鬼 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #f7f5ef;
color: #24292f; margin: 0; line-height: 1.75; }
.wrap { max-width: 660px; margin: 22px auto; background: #fff;
border: 1px solid #d8d3c8; border-radius: 14px; padding: 20px 24px; }
h1 { font-size: 20px; margin: 0 0 10px; }
p { font-size: 14px; margin: 0 0 10px; }
code { color: #b0342a; }
.blk { border: 1px solid #eadfc7; border-radius: 10px; padding: 12px 14px;
margin-bottom: 10px; background: #fbfaf6; }
.hit { border-color: #b0342a; background: #fdf0ee; }
.lbl { display: inline-block; font-size: 12px; padding: 1px 8px;
border-radius: 999px; background: #b0342a; color: #fff; margin-left: 6px; }
.lbl.grey { background: #9a958a; }
.foot { font-size: 13px; color: #6b7280; }
</style>
</head>
<body>
<div class="wrap">
<h1>页面里有两块都叫 top</h1>
<p>右下角那个"回到顶部",点了之后停在下面这块红框上,而不是真正的页首:</p>
<div class="blk">页首:这里其实没有任何 id<span class="lbl grey">你期望它跳来这</span></div>
<div class="blk hit"><code><div id="top"></code> 第 2 块:先撞上的是我<span class="lbl">它实际跳来这</span></div>
<div class="blk"><code><div id="top"></code> 页尾:这个名字这里也有一份,轮不到</div>
<p class="foot">验证方式:在 F12 的 Elements 面板里搜 <code>id="top"</code>,数一下有几处。多于 1 处就是它------同名 id 只会命中第一个,后者被静默忽略。</p>
</div>
</body>
</html>

一个已过时的老写法,看到别照抄
老教程里你会见到 <a name="top"></a> 单独钉一个空标签当锚点,那是 HTML 4 时代的"具名锚点",name 当年的作用就是今天的 id。它在现行 HTML5 里已废弃,靠浏览器兼容才没塌掉;新代码直接给目标元素挂 id 就行,不用额外插一个空标签。属于"知道来龙去脉,不照着写"那一类。
接上昨天那半件事:站内锚点别加 target,也别写空的 href="#"
昨天那张清单第 4 条我埋过一句"锚点、mailto:、tel: 这三类,默认别加 target"。今天正好接上,把那句话的因果补全:锚点天生是"原地走",给它加 target="_blank" 的意思变成"再开一份这个页面,然后在新的那份里滚到那一段"------同一个页面开出两个标签,用户的后退按钮立刻就不知道自己在哪了哈。
为什么?因为 target 管的是"在哪开",#id 管的是"开完站哪",这是两层的参数,叠起来只会互相拆台。两栏并排跑一遍你就明白了:
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>站内锚点的两种写法:裸 #id 与 加了 target 的 #id</title>
<style>
/* 左栏是正确写法,右栏是"给锚点加了 target=_blank"的过度设计 */
/* 两栏渲染出来一模一样,差别要点下去才看得见------多开一个标签 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #f7f5ef;
color: #24292f; margin: 0; line-height: 1.8; }
.wrap { max-width: 760px; margin: 24px auto; background: #fff;
border: 1px solid #d8d3c8; border-radius: 14px; padding: 22px 26px; }
h1 { font-size: 21px; margin: 0 0 12px; }
.cols { display: flex; gap: 18px; flex-wrap: wrap; }
.col { flex: 1; min-width: 300px; border: 1px solid #d8d3c8;
border-radius: 10px; padding: 14px 16px; }
.bad { border-color: #b0342a; }
.good { border-color: #2f4a22; }
.tag { display: inline-block; font-size: 12px; padding: 2px 10px;
border-radius: 999px; color: #fff; margin-bottom: 8px; }
.tag.b { background: #b0342a; }
.tag.g { background: #2f4a22; }
h2 { font-size: 16px; margin: 0 0 6px; }
p { font-size: 14px; margin: 0 0 8px; }
a { color: #1b4055; }
code { color: #b0342a; }
.blk { border: 1px dashed #c9c3b6; border-radius: 8px; padding: 10px 12px;
margin-top: 8px; font-size: 13px; background: #fbfaf6; }
.foot { font-size: 13px; color: #6b7280; margin-top: 14px; }
</style>
</head>
<body>
<div class="wrap">
<h1>同一个"跳到第二节",两种写法对着看</h1>
<div class="cols">
<div class="col good">
<p><span class="tag g">该这么写</span></p>
<p><a href="#part-b">跳到第二节</a></p>
<p>只写 <code>href="#part-b"</code>:原地滚过去,不产生新标签。</p>
</div>
<div class="col bad">
<p><span class="tag b">别这么写</span></p>
<p><a href="#part-b" target="_blank">跳到第二节(多塞了 target)</a></p>
<p>写了 <code>target="_blank"</code>:又开一份本页,在新那份里滚到第二节。</p>
</div>
</div>
<div class="blk" id="part-b">第二节:两条链接的目标都是这块。点左边那条,你只有这一个标签;点右边那条,标签栏会多出一张一模一样的本页。</div>
<p class="foot">现象验证:点完右边那条,数一下浏览器标签栏------两份同一个文件、同一个 <code>#part-b</code>。这就是昨天清单第 4 条要防的"过度使用",风险不在安全,在体验。</p>
</div>
</body>
</html>


运行结果对照:左栏点完还在这一页,右栏点完标签栏多出一张------这一下就把昨天那句话钉牢了。什么时候才该写 target?把用户送出你站子的时候,而且得配上 rel。
| 你想干什么 | 该写 | 别写 |
|---|---|---|
| 本页内上下走 | href="#how-id" |
href="#how-id" target="_blank" |
| 跳到另一页的某一段 | href="day022.html#part-3" |
href="#part-3"(对面那页的东西找不到) |
| 只回顶部,页首没 id | 给页首容器补 id="top" |
href="#"(只清片段,位置含糊) |

顺带说清:锚点不是导航的全部
你现在心里应该在想:这不就是把 Day 18 那条普通链接换了个尾巴吗?对,机制就是同一条 <a>,差别只在有没有 #、要不要出这份文档。所以它适用的是"同一页里内容很多"的场景;如果你的内容本来就分散在几页,那该走页面跳转,别硬凑一页超长文嘛------那属于"用对了地方才省劲",不是通用解。
今日产出:一篇带目录、五段 id 和回顶链接的长文页
把今天这套东西落到大纲要的那份产出上------一篇能靠目录在长文里上下走的页面:
Plaintext
<!-- 《100天精通HTML基础入门》Day 21 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的长文页 · 目录 + 五段 id + 回顶</title>
<style>
/* 目录里每条链接都必须能对上下面某一块的 id,一一对应不许落单 */
/* 样式不用细看,理解为给网页化妆即可,CSS 专栏里会展开 */
* { box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; background: #f7f5ef;
color: #24292f; margin: 0; line-height: 1.85; }
.wrap { max-width: 700px; margin: 24px auto; background: #fff;
border: 1px solid #d8d3c8; border-radius: 14px; padding: 26px 30px; }
h1 { font-size: 23px; margin: 0 0 4px; }
.meta { font-size: 13px; color: #8a8a86; margin: 0 0 16px; }
h2 { font-size: 17px; margin: 0 0 8px; color: #1b4055;
border-left: 4px solid #ffd166; padding-left: 8px; }
p { font-size: 14.5px; margin: 0 0 10px; }
a { color: #1b4055; }
nav { background: #fbf7e8; border: 1px solid #e6dcc0; border-radius: 12px;
padding: 12px 18px; margin-bottom: 18px; }
nav ol { margin: 0; padding-left: 20px; }
nav li { font-size: 14px; margin-bottom: 4px; }
.sec { border: 1px solid #e7e1d5; border-radius: 12px; padding: 16px 18px;
margin-bottom: 14px; scroll-margin-top: 12px; }
.sec:target { border-color: #d9a441; background: #fffaf0; }
.gap { height: 300px; border: 1px dashed #ded7c8; border-radius: 10px;
color: #b1a99a; font-size: 13px; display: flex; align-items: center;
justify-content: center; margin-bottom: 14px; }
.up { font-size: 13px; text-align: right; margin: 6px 0 0; }
</style>
</head>
<body>
<div class="wrap" id="top">
<h1>我在两周内把 100 篇 HTML 笔记整理成了一页</h1>
<p class="meta">Day 21 · 一份带页内目录的长文页,点目录就能走到那一段</p>
<nav>
<ol>
<li><a href="#why-long">为什么要写长文页</a></li>
<li><a href="#how-id">id 是怎么起的</a></li>
<li><a href="#how-nav">目录那一段怎么写</a></li>
<li><a href="#how-back">回顶链接为什么不用脚本</a></li>
<li><a href="#check">上线前我自己怎么验收</a></li>
</ol>
</nav>
<div class="gap">这里是正文第一段前的长内容(撑出滚动距离,方便看跳转)</div>
<div class="sec" id="why-long">
<h2>为什么要写长文页</h2>
<p>把一篇长东西放在一页里,读者不用来回点;代价是他得滚很久,所以目录必须能走。</p>
<p class="up"><a href="#top">回到目录 ↑</a></p>
</div>
<div class="sec" id="how-id">
<h2>id 是怎么起的</h2>
<p>我给每块起的是"这节讲什么"的名字,全小写、词中间用减号:why-long、how-id。</p>
<p class="up"><a href="#top">回到目录 ↑</a></p>
</div>
<div class="sec" id="how-nav">
<h2>目录那一段怎么写</h2>
<p>目录用 ol 包着,每条 a 只写 href="#对应的id",不加 target------站内跳转新开标签很招人烦。</p>
<p class="up"><a href="#top">回到目录 ↑</a></p>
</div>
<div class="gap">正文中间还有一大段插图和数据表,这里省略成一块灰区</div>
<div class="sec" id="how-back">
<h2>回顶链接为什么不用脚本</h2>
<p>因为页首那块容器本身有 id="top",一条 href="#top" 就够了,不需要一行 JS。</p>
<p class="up"><a href="#top">回到目录 ↑</a></p>
</div>
<div class="sec" id="check">
<h2>上线前我自己怎么验收</h2>
<p>把目录逐条点一遍,五条都要滚到不同的位置;再搜一遍源码,数 id 是不是五个不重复。</p>
<p class="up"><a href="#top">回到目录 ↑</a></p>
</div>
</div>
</body>
</html>


运行结果我是逐条点过才写这段的:目录五条,每条都滚到各自那块,落点那块的边框会变成淡金色(:target 在替你验收)。对比数据很直白------五条链接、五个 id、零次报错。样式那部分不用细看,理解为给网页化妆即可,CSS 专栏里会展开。有个边界注意一下:那五块前面塞了两条灰色长块撑滚动距离,你把自己的真实正文放进去以后,如果某一节本来就在首屏里,点它的目录看起来"没动",那不是 bug,它本来就摆在你眼前呢。
小结:回头看锚点这三条硬规矩
-
锚点是两半的活:目标有
id,链接写href="#同一个id";差一个字母、差一处大小写,现象都是"点了没反应、地址栏也不变",只能自己搜。 -
它不报错是有原因的:浏览器只做"按名字找元素"这一件事,找不到就没有可报的失败;这跟 Day 19、Day 20 那两次"低声处理"是同一套性格。
-
id全页唯一、无空格、不以数字开头,跨页得写另一页.html#id,站内锚点不要配target。
两道想想的问题:一,如果目录有六条、正文只有五个 id,少的那条会出现什么现象,你能不能一眼说出验证方法?二,把 href="#top" 换成 href="day021.html#top" 会多付出什么代价?想通这两条,今天这块才算真过了。
动手改一改:名片页上三个必改项
啪,对着你 Day 14 那份名片页(或者 Day 20 的阅读页)下手,三件事一遍过完:
-
给页首那块容器补
id="top",页尾加一条<a href="#top">回到顶部</a>; -
把页面切成三到五块,每块一个 id,上面加一段目录
ol,逐条点一遍------每条都必须滚到不同位置; -
源码里搜
target="_blank",凡是href="#..."的那几条,把target删掉。
【待补:你的真实经历------如果你带过人或自己有过"目录点了没反应、查半天发现是 id 拼错 / 同名 id 抢了跳转"这类具体事,把当时那页有多长、你怎么发现、修完用了多久补在这里。这一格必须是真事,写不出就留着别编。】
明天见(Day 22):今晚先把这篇文章切成几段想好
明天 Day 22 讲的是"切法"本身------不再是随便拿 div 把内容圈起来,而是用 article、section、header 把一篇文章组织起来,让它自己讲得出"哪部分是标题、哪部分是主体、这一段在说啥"。今晚动手那三步里,你其实已经替它做过准备了:你把长文切成五块、各起了名字,那五块正好就是明天要问"到底该叫它 section 还是 div"的东西。就这么着,明天见。
参考资料