Day 21 _ 页内锚点_给每段起个 id,目录写 href=_#id_,点一下页面就滚到那一段

Day 21 | 页内锚点:给每段起个 id,目录写 href="#id",点一下页面就滚到那一段

本文是《100天精通HTML基础入门》专栏第 21 篇。昨天刚把"把用户送出门"这件事上了锁,今天调头回站内------不跳页、不开窗,就在同一份文档里上下来回走。
摘要 :本文基于 HTML5 现行规范与浏览器对 URL 片段标识(#id)的处理,实测环境 Chrome 128。锚点是两半的活:目标元素挂一个 id,目录链接写 href="#同一个id",点一下页面就滚到那一段。我把两份只差一个字母的代码并排跑了一遍:拼错那份的表现是跳不动、不报错、地址栏也不变,典型的无声失败。这套片段定位机制多年没变,学了长期适用;<a name="..."> 是 HTML 4 的已废弃老写法,看到别照抄。适合刚把 targetrel 理顺、正要动手写长文页的你。

本文目录

  • 先摆一组对照:同一份目录,一份跳得动,一份原地装死

  • 锚点是两半的活: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-1id="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>&lt;div class="blk" <i>id="section"</i>&gt;</em>第三段正文......&lt;/div&gt;</div>
    <div><em>&lt;a <i>href="#sectoin"</i>&gt;</em>第三段&lt;/a&gt;</em></div>
    <div><em>// 左边 section,右边 sectoin ------ 差一个字母的位置,浏览器不认为它们是同一个名字</em></div>
  </div>
  <div class="warn">控制台里没有红字。这一类失败不抛异常,只表现为"点了没反应"------所以只能靠你自己搜,别指望它提示你。</div>
</body>
</html>

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

三步排查:id 到底存不存在

按这个顺序走,别一上来就改代码。

  1. 点一下目录,先看地址栏有没有冒出 #xxx。没有,说明链接本身可能压根不是锚点写法(比如漏了 #)。

  2. 冒出来了,页面却没滚:把 # 后面那串复制下来,按 Ctrl+F 在源码里搜,看它是不是只出现在链接里、而目标元素身上没有。

  3. 两处都有但差一个字母或大小写不一致:以目标元素的 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>&lt;div id="top"&gt;</code> 第 2 块:先撞上的是我<span class="lbl">它实际跳来这</span></div>
    <div class="blk"><code>&lt;div id="top"&gt;</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 的阅读页)下手,三件事一遍过完:

  1. 给页首那块容器补 id="top",页尾加一条 <a href="#top">回到顶部</a>;

  2. 把页面切成三到五块,每块一个 id,上面加一段目录 ol,逐条点一遍------每条都必须滚到不同位置;

  3. 源码里搜 target="_blank",凡是 href="#..." 的那几条,把 target 删掉。

【待补:你的真实经历------如果你带过人或自己有过"目录点了没反应、查半天发现是 id 拼错 / 同名 id 抢了跳转"这类具体事,把当时那页有多长、你怎么发现、修完用了多久补在这里。这一格必须是真事,写不出就留着别编。】

明天见(Day 22):今晚先把这篇文章切成几段想好

明天 Day 22 讲的是"切法"本身------不再是随便拿 div 把内容圈起来,而是用 articlesectionheader 把一篇文章组织起来,让它自己讲得出"哪部分是标题、哪部分是主体、这一段在说啥"。今晚动手那三步里,你其实已经替它做过准备了:你把长文切成五块、各起了名字,那五块正好就是明天要问"到底该叫它 section 还是 div"的东西。就这么着,明天见。

参考资料


相关推荐
子兮曰2 小时前
Bun v1.4.1 深度解析:从 Zig 到 Rust,一场 11 天、64 个 AI 代理的语言迁徙
前端·后端·bun
人民广场吃泡面2 小时前
什么是AI Agent?它又能给前端带来哪些效率提升?
前端·人工智能
中科三方2 小时前
两家域名注册商资质被ICANN终止:企业域名资产安全再受关注
前端·网络·安全·域名
bug总结3 小时前
uniapp vue3全局方法注册使用
前端·javascript·uni-app
华无丽言3 小时前
如何在宜搭中实现获取子表中的字段值赋值到父表中?
前端·javascript·低代码
IT_陈寒4 小时前
Vue的computed属性竟然坑了我一把
前端·人工智能·后端
威斯软科的老司机4 小时前
通俗讲解 CNN 图像识别、向量 Embedding、Softmax 概率计算这三块的简化原理
前端·人工智能·ui·数字孪生
泯泷5 小时前
手搓JSVM第 12 篇:完整最小 JSVM 实现与源码设计复盘
前端·javascript·前端框架
默_笙5 小时前
🍔 给 AI 发一张结构化表格(下):从"自由发挥"到"按格填空"的输出驯服指南
前端·javascript