普通视图

发现新文章,点击刷新页面。
昨天 — 2026年4月24日首页

别再背“var 提升,let/const 不提升”了:揭开暂时性死区的真实面目

2026年4月24日 18:11

别再背“var 提升,let/const 不提升”了:揭开暂时性死区的真实面目

你可能听过:“var 有变量提升,letconst 没有。”
但当你写 console.log(x); let x = 1; 报错时,真的就是“没提升”吗?
这篇文章会帮你彻底搞懂提升、暂时性死区(TDZ)以及它们背后的设计原因。


1. 一个常见的“误解”

很多 JS 入门教程会告诉你:

  • var 有变量提升,可以在声明前访问(值为 undefined)。
  • letconst 没有变量提升,声明前访问会报错。

于是你记住了结论,但一遇到下面的代码又开始困惑:

let x = 1;
function test() {
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

如果 let 真的“不提升”,为什么输出不是外层的 1 呢?
这恰恰说明:letconst 其实也提升了,只是行为不同。


2. 什么是“提升”?

JavaScript 引擎在执行代码前,会先进行编译阶段。在这个阶段,它会将所有变量和函数的声明移动到当前作用域的顶部。这个过程就叫提升(Hoisting)

注意:提升的是声明,而不是赋值。

2.1 函数声明的提升

函数声明会被整体提升,所以你可以在声明之前调用函数

sayHello(); // 输出 "Hello"

function sayHello() {
  console.log("Hello");
}

因为引擎实际看到的代码是顺序是:

function sayHello() { console.log("Hello"); }
sayHello();

2.2 var 的变量提升

var 声明的变量也会被提升,但只提升声明,不提升赋值,初始值为 undefined

console.log(a); // undefined(不是报错)
var a = 10;

实际效果:

var a;           // 提升到顶部,初始值 undefined
console.log(a);
a = 10;

3. letconst 真的“不提升”吗?

先看这段代码:

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;

如果 let 完全不提升,那么 b 在声明前应该根本不存在,错误应该是 b is not defined(未声明的变量错误)。
但实际错误是 “Cannot access before initialization”(初始化前无法访问)。这暗示了:引擎已经知道 b 存在于当前作用域,只是不允许你在它初始化之前使用

同样的现象也出现在 const 上。

3.1 暂时性死区(TDZ)

实际上,letconst 也会提升。但它们有一个额外的限制:从进入作用域到声明语句之间,变量处于“暂时性死区”(Temporal Dead Zone, TDZ)。在这期间访问变量会抛出 ReferenceError

所以,更准确的描述是:

  • var:提升 + 初始化为 undefined
  • let / const:提升 + 不初始化,且在声明前禁止访问

4. 为什么要有“暂时性死区”?直接不提升不行吗?

你可能会想:既然声明前不让用,那不如干脆不提升,让变量在声明前不存在,不是更简单?

4.1 首先,JavaScript 做不到“不提升”

JavaScript 采用词法作用域(也叫静态作用域),变量的作用域在编译时就已经确定了。为了知道一个标识符到底属于哪个作用域(是全局、函数内还是块内),引擎必须在编译阶段就把所有变量声明注册到对应的作用域。这个注册过程就是“提升”。

例如:

let x = 1;
{
  let x = 2;
}

如果没有编译阶段的注册,内部的 x 就无法与外部 x 区分开,作用域规则就乱套了。因此,无论 varlet 还是 const,都必须提升(即注册到作用域)

4.2 如果“不提升”,会出现什么灾难?

假设 JavaScript 真的让 let 完全不提升,即在声明前它不注册到当前作用域。那么看这段代码:

let x = 1;
function test() {
  console.log(x); // 按“不提升”的假设,这里应该去外层找 x
  let x = 2;
}
test();

如果引擎在编译时没有把内部的 x 注册到 test 函数作用域,执行到 console.log(x) 时,它会沿着作用域链向外查找,找到全局的 x = 1。然后输出 1,再执行 let x = 2 声明一个局部变量。

这会导致极其隐蔽的 bug:开发者以为内部声明了一个局部变量,但实际上却意外地访问到了外层的变量。这与 let 的设计宗旨——变量必须声明后才能使用,且不与上层作用域混淆——完全相悖。

4.3 TDZ 正是为了解决这个问题

let / const 的设计方案是:

  1. 编译阶段:将变量提升到当前作用域顶部(注册),但标记为“未初始化”。
  2. 执行阶段:从作用域顶部到声明语句之间,形成 TDZ,任何访问都报错。
  3. 执行到声明语句
    • 如果有初始化(let x = 10),则此时变量被初始化并赋值。
    • 如果只有声明(let x;),则初始化为 undefined

这样既保证了变量在声明前不会意外访问到外层同名变量(因为引擎知道当前作用域有这个变量,不会向外找),又强制你必须先声明后使用,代码更安全、更可预测。


5. 一个直观对比

声明方式 是否提升 初始值 声明前访问 表现
函数声明 ✅ 整体提升 函数体 ✅ 可以 正常调用
var ✅ 提升 undefined ✅ 可以(值为 undefined 不报错,但可能拿到意外值
let ✅ 提升(但 TDZ) ❌ 报错 ReferenceError: Cannot access before initialization
const ✅ 提升(但 TDZ) ❌ 报错 同上,且必须声明时初始化

6. 最佳实践建议

  • 默认使用 const,只有当变量需要被重新赋值时才用 let
  • 禁止使用 var,除非你明确需要利用它的提升特性(极少场景)。
  • 在作用域顶部声明变量,避免 TDZ 带来的困扰(虽然 TDZ 是规范,但写成先声明后使用是最清晰的)。

7. 总结

  • 所有声明(varletconst、函数声明)都会提升,本质是编译阶段将变量/函数注册到作用域。
  • var 在提升时初始化为 undefined,允许提前访问(但容易导致 bug)。
  • let / const 也提升,但进入 TDZ,在声明前访问会报错,强制你先声明后使用。
  • TDZ 的存在是为了在不破坏词法作用域的前提下,避免“变量泄漏”到外层作用域,同时提供更严格的编程约束。
  • 下次面试官问你“letconst 有变量提升吗?”,你可以自信地回答:“有的,但存在暂时性死区。”

💬 互动:你在实际开发中遇到过因 TDZ 导致的 bug 吗?评论区分享你的经历,我们一起避坑。

(完)

昨天以前首页

前端快速上手保姆级教程day5: 响应式布局

2026年4月22日 09:59

Day5 学习文档:响应式布局(Responsive)

1. 今天要掌握什么

Day5 的目标是:同一套页面在手机、平板、桌面都可读、可点、可用。

  • 理解“响应式”不是缩放,而是布局策略切换
  • 掌握媒体查询 @media 的基本写法
  • 学会移动优先(mobile-first)思路
  • 能处理 3 个高频问题:横向滚动、图片溢出、点击区域过小

2. 什么是响应式(大白话)

大白话:
页面会根据屏幕宽度,自动换一套更合适的排版。

不是把桌面页面硬缩小,而是让布局“变形”:

  • 手机:单列优先,按钮更大,字更清楚
  • 平板:间距更宽,内容更舒展
  • 桌面:可以多列,提高信息密度

3. 核心概念

3.1 视口(viewport)

你看到网页的窗口区域。移动端一定要有:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

否则手机会用“虚拟宽屏”渲染,页面会小得像蚂蚁。

3.2 断点(breakpoint)

断点就是“在哪个宽度开始切换布局”的分界线。

常见练习断点(不是唯一标准):

  • 375:小手机
  • 768:平板
  • 1024:桌面

3.3 媒体查询(media query)

@media (min-width: 768px) {
  /* 宽度 >= 768 时生效 */
}

4. 移动优先(mobile-first)

推荐顺序:

  1. 先写手机默认样式(不加媒体查询)
  2. 再用 min-width 给平板/桌面增强

好处:

  • 代码更清晰,层层增强
  • 小屏体验不会被遗漏

示例:

/* 默认:手机 */
.card-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 平板及以上 */
@media (min-width: 768px) {
  .card-list {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* 桌面及以上 */
@media (min-width: 1024px) {
  .card-list {
    grid-template-columns: repeat(3, 1fr);
  }
}

4.1 Grid 布局核心知识(Day5 重点补充)

你在 Day5 页面里已经用了 display: grid,这里把 Grid 的核心概念补齐。

4.1.1 Grid 是什么?

大白话:
Grid 是“切格子”的布局系统,适合做二维布局(行 + 列一起控制)。

你可以把容器想象成棋盘,把内容块放进格子里。

4.1.2 三个最常用属性

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
  • display: grid:开启 Grid 布局
  • grid-template-columns:定义列数与列宽
  • gap:网格项之间的间距

4.1.3 1fr 是什么?

fr = fraction(份数)。
1fr 1fr 1fr 就是“分成 3 份,每份一样宽”。

示例:

  • grid-template-columns: 1fr 1fr -> 两列等宽
  • grid-template-columns: 2fr 1fr -> 左边是右边 2 倍宽

4.1.4 repeat() 为什么常用?

grid-template-columns: repeat(3, 1fr);

等价于:

grid-template-columns: 1fr 1fr 1fr;

repeat() 更简洁,改列数也更方便。

4.1.5 你当前 Day5 的 Grid 切换逻辑

/* 手机 */
.cards { grid-template-columns: 1fr; }

/* 平板 */
@media (min-width: 768px) {
  .cards { grid-template-columns: repeat(2, 1fr); }
}

/* 桌面 */
@media (min-width: 1024px) {
  .cards { grid-template-columns: repeat(3, 1fr); }
}

这段就是标准响应式 Grid:
先单列,再多列,跟屏幕宽度同步增强。

4.1.6 Grid 和 Flex 怎么选?

  • 一维排队(单行/单列内部对齐) -> 优先 Flex
  • 二维切区(行列同时控制) -> 优先 Grid

Day5 常见组合:

  • 顶部导航用 Flex
  • 卡片区用 Grid

4.1.7 Grid 常见坑

  1. 列太多,小屏被挤爆

    • 解法:移动端先单列,断点再加列
  2. 忘记设置 gap,卡片贴太紧

    • 解法:统一用 gap 管理间距
  3. 子项内容太长撑破格子

    • 解法:检查内容换行、图片自适应、最小宽度策略

5. Day5 三大高频问题与解法

5.1 横向滚动条(最常见)

常见原因:

  • 写死宽度(例如 width: 960px
  • 元素 width: 100% 同时大 padding(没用 border-box
  • fixed 元素太宽或位置越界

排查建议:

  • 先检查“谁比屏幕宽”
  • DevTools 切 375 宽度逐个排查容器

5.2 图片撑爆容器

统一加:

img {
  max-width: 100%;
  height: auto;
  display: block;
}

5.3 手机点击困难

建议:

  • 文字不小于 14px(正文建议 16px)
  • 按钮/链接适当增大 padding
  • 相邻可点击元素留足间距

6. 你可以直接套用的响应式骨架

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  line-height: 1.5;
}

.container {
  width: min(100%, 960px);
  margin: 0 auto;
  padding: 12px;
}

/* 手机:默认单列 */
.layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 平板 */
@media (min-width: 768px) {
  .container {
    padding: 16px;
  }
  .layout {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* 桌面 */
@media (min-width: 1024px) {
  .container {
    padding: 20px;
  }
  .layout {
    grid-template-columns: repeat(3, 1fr);
  }
}

7. Day5 自测清单

  • 375 / 768 / 1024 三档可正常阅读
  • 页面无横向滚动条
  • 图片不会撑爆容器
  • 导航和按钮在手机上容易点击
  • 至少 2 个模块实现断点切换

8. 常见误区

  1. 只在桌面看效果

    • 结果:上线后手机体验崩
  2. 断点过多且混乱

    • 建议先用 2~3 个关键断点
  3. 只改字体不改布局

    • 响应式核心是“结构变化”,不是单纯放大缩小
  4. 一上来追求完美设备适配

    • 先保证主流宽度可用,再逐步细化

9. 今日复盘模板(可复制到 README)

## Day5 学习总结(Responsive)

### 我做了什么
- 完成移动优先样式
- 增加平板与桌面断点
- 修复图片溢出和横向滚动问题

### 我学会了什么
- @media 的基本用法
- 断点切换布局的思路
- 响应式排错方法

### 我遇到的问题
- (填写)

### 我如何解决
- (填写)

### 下一步
- Day6:JavaScript DOM 与事件交互

10. 断点写法(基础版)与进阶写法

10.1 断点写法(你当前页面使用的是这种)

思路是“手动指定每个阶段的列数”:

/* 手机默认 */
.cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* 平板 */
@media (min-width: 768px) {
  .cards {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* 桌面 */
@media (min-width: 1024px) {
  .cards {
    grid-template-columns: repeat(3, 1fr);
  }
}

优点:

  • 直观,适合入门
  • 每个断点下的布局可控

缺点:

  • 断点多时维护成本上升
  • 设备尺寸变化时,可能要不断补新断点

10.2 进阶写法:auto-fit + minmax

思路是“声明卡片最小宽度,让列数自动计算”:

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}

大白话解释:

  • minmax(220px, 1fr):每张卡片最小 220px,空间够就继续拉伸
  • auto-fit:一行能放几张就自动放几张

这样通常可以少写甚至不写卡片区列数断点。

10.3 auto-fit vs auto-fill(快速区分)

  • auto-fit:会“收起空列”,让已有卡片拉伸占满空间(更常用)
  • auto-fill:会保留空列轨道,可能看到“留槽位”的感觉

入门建议:

  • 卡片网格优先用 auto-fit

10.4 实战建议(Day5)

你可以先保留当前断点版(便于理解),再开一个分支或副本改成进阶版对比:

  1. 断点版:训练“响应式思维”
  2. 进阶版:训练“自动适配思维”

两种都掌握,后面做项目会很稳。


附录:完整 index.html 源代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Day5 Responsive 实战</title>
    <style>
      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
        line-height: 1.5;
        color: #1f2937;
        background: #f5f7fb;
      }

      .container {
        width: min(100%, 960px);
        margin: 0 auto;
        padding: 12px;
      }

      .topbar {
        background: #fff;
        border-bottom: 1px solid #e5e7eb;
      }

      .topbar-inner {
        width: min(100%, 960px);
        margin: 0 auto;
        padding: 10px 12px;
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        align-items: center;
        justify-content: space-between;
      }

      .topbar nav {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
      }

      .topbar a {
        text-decoration: none;
        color: #1d4ed8;
        padding: 6px 10px;
        border-radius: 8px;
      }

      .topbar a:hover {
        background: #dbeafe;
      }

      .hero,
      .card,
      .tips {
        background: #fff;
        border: 1px solid #e5e7eb;
        border-radius: 10px;
      }

      .hero {
        padding: 16px;
        margin-bottom: 12px;
      }

      .cards {
        display: grid;
        grid-template-columns: 1fr;
        gap: 12px;
      }

      .card {
        padding: 14px;
      }

      .tips {
        margin-top: 12px;
        padding: 14px;
      }

      img {
        max-width: 100%;
        height: auto;
        display: block;
        border-radius: 8px;
      }

      @media (min-width: 768px) {
        .container {
          padding: 16px;
        }

        .cards {
          grid-template-columns: repeat(2, 1fr);
        }
      }

      @media (min-width: 1024px) {
        .container {
          padding: 20px;
        }

        .cards {
          grid-template-columns: repeat(3, 1fr);
        }
      }
    </style>
  </head>
  <body>
    <header class="topbar">
      <div class="topbar-inner">
        <strong>Day5 Responsive 实战</strong>
        <nav>
          <a href="#intro">介绍</a>
          <a href="#cards">卡片区</a>
          <a href="#tips">检查点</a>
        </nav>
      </div>
    </header>

    <main class="container">
      <section class="hero" id="intro">
        <h1>响应式布局练习页</h1>
        <p>默认手机单列,平板双列,桌面三列。请用 DevTools 切换 375 / 768 / 1024 验证布局变化。</p>
      </section>

      <section class="cards" id="cards">
        <article class="card">
          <h2>模块 A</h2>
          <p>移动端单列,保证可读性。</p>
        </article>
        <article class="card">
          <h2>模块 B</h2>
          <p>平板开始并排,提升空间利用率。</p>
        </article>
        <article class="card">
          <h2>模块 C</h2>
          <p>桌面三列展示,提高信息密度。</p>
        </article>
        <article class="card">
          <h2>模块 D</h2>
          <p>继续观察不同断点下卡片数量变化。</p>
        </article>
        <article class="card">
          <h2>模块 E</h2>
          <p>练习时可替换为真实内容模块。</p>
        </article>
        <article class="card">
          <h2>模块 F</h2>
          <p>确保手机下无横向滚动条。</p>
        </article>
      </section>

      <section class="tips" id="tips">
        <h2>自测提示</h2>
        <ul>
          <li>375px:单列,无横向滚动。</li>
          <li>768px:双列,间距舒适。</li>
          <li>1024px:三列,内容不拥挤。</li>
        </ul>
      </section>
    </main>
  </body>
</html>

新手小白学前端day4: 半小时彻底搞懂Position

2026年4月21日 17:48

Day4 学习文档:Position 定位实战

1. 今天要掌握什么

Day4 的目标是把“元素放哪儿”这件事彻底搞明白:

  • 理解 relativeabsolutefixedsticky 的区别
  • 知道每种定位在什么场景下最合适
  • 能做出 3 个常见交互:吸顶导航、角标、回到顶部按钮

2. 大白话理解 Position

可以把页面想象成一张地图,普通元素按“排队规则”从上到下放置。
position 就是告诉浏览器:这个元素是否要“偏离原队列”。

  • static:默认值,老老实实排队
  • relative:还在队列里,但允许“微调位置”
  • absolute:脱离队列,贴着某个参考盒子定位
  • fixed:脱离队列,直接贴着屏幕定位
  • sticky:平时排队,滚动到阈值后吸附

2.1 两个必须懂的基础词:viewport 和文档流


一、文档流(Normal Flow)

大白话:就是网页里的元素“排队”的方式,默认情况下,它们按照你在HTML里写的顺序,一个接一个地自动摆放。

  • 块级元素(比如 <div><p><h1>):就像地铁里的车厢,每个独占一整节,竖着排,上一个在顶上,下一个在底下,不会并排。
  • 行内元素(比如 <span><a><strong>):就像排队买票的人,大家并排站在一起,从左到右,一行不够了就自动换到下一行。

一句话:文档流就是“正常排队”,你什么都不改,它就这么排。


二、viewport(视口)

大白话:就是你手机或电脑屏幕上用来显示网页的那个矩形区域

  • 在电脑上,视口就是浏览器窗口的内部(不包括工具栏、地址栏)。
  • 在手机上,视口比较特殊:因为手机屏幕窄,早期网页都是为电脑设计的(宽度至少960px),如果手机用真实屏幕宽度(比如375px)去显示,网页会被挤得乱七八糟。所以手机浏览器默认用一个虚拟的宽视口(通常是980px)来加载网页,然后缩放显示。这就导致你看不清字,需要手动放大。

**<meta name="viewport"> 标签的作用**:告诉手机浏览器:“别用那个虚拟宽视口了,就用我的实际屏幕宽度来布局”。常见写法:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

意思是:

  • width=device-width:把视口宽度设成手机屏幕的实际宽度(比如375px)。
  • initial-scale=1.0:不缩放,1个CSS像素对应1个屏幕物理像素。

一句话:viewport 就是“你看网页的那个框框”,移动端必须设置那个meta标签,否则网页会像缩小的蚂蚁。


三、二者关系

  • 文档流 是元素在视口内的排列方式。
  • 视口 是容纳文档流的容器大小。

举个例子:你把一排积木(文档流)放在一个盒子里(视口)。盒子宽了,积木可能并排;盒子窄了,积木可能换行。

移动端如果不设置 viewport,盒子(视口)会默认宽980px,导致你的积木(元素)看起来很迷你,用户必须双指缩放才能看清。设置了之后,盒子宽度就是手机屏幕宽度,你按正常尺寸写CSS就行。

希望这样解释你彻底明白了。如果还有模糊的地方,可以继续问。


3. 四种核心定位方式(更直观版)

先记一句总口诀:

  • relative站在原地,可微调
  • absolute脱离队伍,找爹定位
  • fixed钉在屏幕,不跟页面走
  • sticky先正常滚,后吸顶

3.1 position: relative(站在原地,可微调)

生活类比:
你在排队时,脚还在原位置,但身体可以往前后左右挪一点。

你会看到的效果:

  • 这个元素原本位置还占着,不会让后面的元素补上来
  • 可以用 top/right/bottom/left 微调显示位置

最常见用途(不是“挪自己”,而是“给孩子当参照物”):

  • 给卡片加 position: relative,让内部角标用 absolute 对齐这张卡片

一句话判断:
需要一个“定位锚点”时,先上 relative

3.2 position: absolute(脱离队伍,找爹定位)

生活类比:
一个人离开队伍,跑去贴着“最近的定位锚点”站位。

你会看到的效果:

  • 元素脱离文档流,原位置会被其他元素“顶上来”
  • 它会找最近的、position 不是 static 的祖先作为参照
  • 如果没找到,就相对页面定位(常见“跑飞”)

典型场景:

  • 卡片角标、关闭按钮、图片文案浮层

一句话判断:
想让元素“贴着某个盒子角落”时,用 absolute + 父级 relative

3.3 position: relativeabsolute 的关系(大白话)

3.3.1 relative(相对定位)

大白话:元素原本在文档流里占着位置(排队占位),你可以通过 top/left/right/bottom 让它相对于自己的原位置挪动一下。挪动后,原来的坑位依然空着(其他元素不会挤过来)。

  • 常用场景:给绝对定位的父元素做“参照物”。
3.3.2 absolute(绝对定位)

大白话:元素完全脱离文档流(不再排队,也不再占位),其他元素会忽略它,直接填补它原来的位置。它的位置参考系是最近的那个设置了 position(非 static)的祖先元素。如果找不到这样的祖先,就参考视口(但严格说是初始包含块,可以理解为视口)。

3.3.3 它们最经典的配合:父 relative,子 absolute

这是为了让子元素相对于父元素进行绝对定位。

例子

<div class="parent" style="position: relative;">
  <div class="child" style="position: absolute; top: 0; right: 0;">角标</div>
</div>
  • 父元素 relative:不挪动自己,只是建立一个定位参照系
  • 子元素 absolute:相对于父元素左上角定位,top:0; right:0 就贴在父元素右上角。

为什么父元素不用 absolute 因为父元素如果 absolute 也会脱离文档流,破坏布局。用 relative 最安全(它不脱离文档流)。

3.3.4 对比表格
特性 relative absolute
是否脱离文档流 ❌ 不脱离,原位置保留 ✅ 脱离,原位置被其他元素占据
定位参照物 自身原本的位置 最近的非 static 祖先(如果没有,则视口)
能否用 top/left 移动 能,相对于自身原位置 能,相对于参照物
常见用途 作为绝对定位的容器、微调位置 浮层、角标、弹出菜单
3.3.5 一个帮你记忆的生活类比
  • **relative**:就像你站在排队的位置上,可以稍微往前探一点身子,但你的脚还在原地(别人不能占你的位)。
  • **absolute**:你从队伍里走出来,站在某个参照物(比如墙壁)旁边。你的原位置立刻被后面的人占了。
  • relative + 子 absolute:你对墙壁(父元素)说:“我要站在你右上角”。墙壁说:“好,我原地不动,你相对于我站。”
3.3.6 一个特殊但重要的点

如果某个祖先元素设置了 transformperspectivefilter 等属性,它会成为 absolute 的参照物(类似 relative),这有时会导致预期外的定位。


总结一句话

  • relative 是“相对自己原位置微调,不脱队”。
  • absolute 是“脱队去找最近的带定位的祖先,没有就找视口”。
  • 组合使用时,父 relative 给子 absolute 当参照物。

3.4 position: fixed(钉在屏幕,不跟页面走)

生活类比:
把便签纸贴在你的手机屏幕上,不管页面怎么滑,便签都在同一个位置。

你会看到的效果:

  • 元素固定在视口(viewport)上
  • 页面滚动时它不动
  • 也脱离文档流,可能遮挡内容

典型场景:

  • 回到顶部按钮
  • 悬浮客服入口

一句话判断:
想“永远在屏幕可见区域”就用 fixed

3.5 position: sticky(先正常滚,后吸顶)

生活类比:
便利贴一开始贴在文档某一行,滚动到顶部后它被“吸”住,不再继续上去。

你会看到的效果:

  • 在阈值前,它和普通元素一样参与文档流
  • 达到阈值(如 top: 0)后,像 fixed 一样吸附
  • 常需要配 z-index 和背景色,避免被内容盖住

典型场景:

  • 吸顶导航
  • 左侧目录跟随

一句话判断:
想要“滚动到某点才固定”,选 sticky


4. 你当前页面里的实战映射

4.1 吸顶导航(sticky)

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
}

解释:导航滚动到页面顶部后会“钉住”。

4.2 角标(relative + absolute)

.badge-card {
  position: relative;
}

.badge {
  position: absolute;
  top: -10px;
  right: -10px;
}

解释:角标相对卡片定位,而不是相对整个页面乱跑。

4.3 回到顶部按钮(fixed)

.back-top {
  position: fixed;
  right: 16px;
  bottom: 16px;
}

解释:无论页面滚动到哪里,按钮都固定在屏幕右下角。


5. 常见坑(重点)

  1. absolute 位置跑偏
  • 原因:父元素没设 position: relative
  1. sticky 不生效
  • 常见原因:没写 top;或父容器有 overflow 限制
  1. fixed 挡住内容
  • 解决:给主内容留底部空间,或调整按钮位置
  1. 层级覆盖异常
  • 解决:配合 z-index 控制层级(前提是元素有定位)

6. 提交前自测清单

  • 顶部导航滚动时可吸附
  • 角标稳定在卡片右上角
  • 回到顶部按钮始终固定在右下角
  • 手机宽度下无明显遮挡或溢出
  • 你能说清这四种定位的适用场景

7. 今天的学习产出模板(可复制到 README)

## Day4 学习总结(Position)

### 我做了什么
- 实现 sticky 吸顶导航
- 实现 relative + absolute 角标
- 实现 fixed 回到顶部按钮

### 我学会了什么
- 四种常见定位方式的差异
- absolute 的参照物查找规则
- sticky 的触发条件

### 我遇到的问题
- (填写你今天遇到的问题)

### 我如何解决
- (填写解决过程)

8. CSS 选择器速记(结合 .fixed-demo

你在 Day4 页面里看到这段:

.fixed-demo {
  position: fixed;
  top: 12px;
  right: 12px;
}

.fixed-demo 里的 . 是什么?

. 表示 class 选择器,意思是:

  • 选中所有 class="fixed-demo" 的元素
  • 给这些元素应用样式

对应 HTML:

<div class="fixed-demo">我是 fixed 对照条</div>

常见选择器符号(新手高频)

  • .demo:class 选择器(最常用)
  • #demo:id 选择器(页面唯一)
  • div:标签选择器
  • A B:后代选择器(A 内任意层级的 B)
  • A > B:子代选择器(A 的直接子元素 B)

一句话记忆

  • . 找 class
  • # 找 id
  • 空格找后代
  • > 找亲儿子(直接子元素)

9. HTML 多个 class:class="card badge-card"

它是什么意思?

class="card badge-card" 表示这个元素同时拥有两个 class

  • card
  • badge-card

在 CSS 里,只要选择器匹配,就会同时应用:

.card { ... }
.badge-card { ... }

为什么要这样写?

常见原因是“组合样式”:

  • card:通用卡片外观(白底、边框、圆角、内边距)
  • badge-card:额外加 position: relative,给角标当定位参照物

这样拆分后,别的卡片可以只复用 card,不必复制一堆样式。

如果两个规则冲突了怎么办?

如果 .card.badge-card 都写了同一个属性(比如 padding),最终生效取决于:

  1. **选择器优先级(specificity)**更高者优先
  2. 优先级相同,通常 后写的规则覆盖先写的规则(同文件内从上到下)

CSS 里还有一种“链式 class”(可选进阶)

.card.badge-card {
  /* 必须同时有 card 和 badge-card 才会命中 */
}

这和 HTML 写多个 class 是配套的,用来表达“只在特定组合下生效”的样式。

更系统的解释请看下一节:## 10. CSS 优先级 + 链式选择器(进阶但很有用)


10. CSS 优先级 + 链式选择器(进阶但很有用)

10.1 链式选择器是什么?

链式选择器(也叫“复合选择器”的一种常见写法):

.card.badge-card { }

含义:元素必须同时满足

  • class="card"
  • 也有 class="badge-card"

所以它比单独的 .card 更“挑剔”,命中范围更小。

对比:

<section class="card">A</section>
<section class="card badge-card">B</section>
  • .card:A 和 B 都会命中
  • .card.badge-card:只有 B 命中

10.2 链式选择器会不会让优先级变高?

会。链式 class 相当于把多个 class 条件叠在一起,通常比单个 class 更具体

直觉记忆:

  • .card:1 个 class
  • .card.badge-card:2 个 class(更具体)

所以在冲突时,.card.badge-card 往往更容易赢过 .card

10.3 CSS 优先级(specificity)到底比什么?

当两条规则都设置了同一个属性(比如 padding),浏览器要决定用哪条,会先看 优先级,再看 书写顺序(同优先级时,后写覆盖先写)。

新手最常用的优先级直觉(从低到高):

  1. 标签选择器(如 divsection
  2. class 选择器(如 .card
  3. id 选择器(如 #intro
  4. 行内样式style="...",一般不推荐大面积使用)
  5. **!important**(尽量别当常规武器)

说明:真实计算比这个更细(还会统计选择器里 class/id/标签的数量),但上面的顺序足够你日常排错。

10.4 两个很容易踩坑的点

  1. 以为“写在后面就一定赢” 不一定。如果对方选择器优先级更高,你写在后面也可能无效。
  2. 链式选择器写错 .card .badge-card(中间有空格)是后代选择器,不是链式选择器。
    .card.badge-card(中间没空格)才是“同时有两个 class”。

10.5 结合你 Day4 页面的一个判断练习

假设同时存在:

.card { padding: 16px; }
.badge-card { padding: 24px; }
.card.badge-card { padding: 20px; }

对一个 class="card badge-card" 的元素:

  • 三个规则都匹配
  • 最终 padding 通常会落在 .card.badge-card(更具体)

如果你发现结果不符合预期,打开 DevTools 看 Computed 面板,能看到最终生效规则与被覆盖原因。


11. border-radius: 999px 是什么“magic”?

它不是魔法,而是一个常见技巧:
把圆角半径写得非常大,让浏览器自动夹到可用最大值。

11.1 为什么写 999px 也不会“溢出”?

浏览器会做限制:圆角不可能超过元素几何允许的范围。
所以你写 999px,实际会被“裁到最大可行圆角”。

效果通常是:

  • 长条按钮 -> 胶囊形(两端很圆)
  • 接近正方形的按钮 -> 接近圆形

11.2 和 border-radius: 50% 有什么区别?

  • 999px:给“很大固定值”,常用于按钮、标签,尺寸变化时也容易保持圆润
  • 50%:按元素自身尺寸比例计算,常用于正方形头像变圆

一句话理解:
999px 更像“我要尽可能圆”,50% 更像“按比例圆”。

11.3 在你 Day4 页面里的实际用途

你的“回到顶部”按钮如果用了:

.back-top {
  border-radius: 999px;
}

它会变成胶囊风格,更像悬浮操作按钮。

11.4 常见使用场景

  • 悬浮按钮(回到顶部、客服入口)
  • 小标签(Tag / Badge)
  • 导航中的胶囊按钮

11.5 小提醒

border-radius 只影响“圆角外观”,不影响元素布局流。
你仍需要配合 paddingline-heightwidth/height 去控制按钮最终形态。


附录:完整 index.html 代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Day4 Position 练习</title>
    <style>
      *, *::before, *::after {
        box-sizing: border-box
      }

      .fixed-demo {
        position: fixed;
        top: 0;
        right: 20px;
        z-index: 100;
        background: yellow;
        padding: 12px 16px;
        border-bottom: 1px solid #e5e5e5;
      }

      .pre-scroll {
        height: 280px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #475569;
        background: linear-gradient(180deg, #dbeafe 0%, #eff6ff 100%);
        border-bottom: 1px solid #bfdbfe;
      }

      .topbar {
        position: sticky;
        top: 0;
        z-index: 10;
        background: #ffffff;
        border-bottom: 1px solid #e5e7eb;
      }

      .topbar-inner {
        max-width: 960px;
        margin: 0 auto;
        padding: 12px 16px;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }

      .topbar a {
        text-decoration: none;
        color: #1d4ed8;
        margin-left: 10px;
      }

      .back-top {
        position:fixed;
        right: 16px;
        bottom: 16px;
        border: 1px solid #1d4ed8;
        border-radius: 999px;
        background: #2563eb;
        color: #fff;
        text-decoration: none;
        padding: 10px 14px;
      }

      .back-top:hover {
        background: #1d4ed8;
      }

      .filler {
        min-height: 1200px;
      }

      .container {
        /* 设置了最大宽度,并且居中显示,并且有内边距 */
        max-width: 960px;
        margin: 0 auto;
        padding: 24px 16px 80px;
      }

      /* 卡片样式 */
      .card {
        background: #fff;
        border: 1px solid #e5e7eb;
        border-radius: 10px;
        padding: 16px;
        margin-bottom: 16px;
      }

      /* 角标卡片样式 */
      .badge-card {
        position: relative;
      }

      /* 角标样式 */
      .badge {
        /* 绝对定位,相对于父元素 */
        position: absolute;
        /* 距离父元素上边10px */
        top: -10px;
        /* 距离父元素右边10px */
        right: -10px;
        /* 背景颜色 */
        background: #ef4444;
        /* 文字颜色 */
        color: #fff;
        /* 字体大小 */
        font-size: 12px;
        /* 内边距 */
        padding: 4px 8px;
        /* 圆角 */
        border-radius: 999px;
      }
    </style>
  </head>
  <body>
    <div class="fixed-demo">我是fixed(一直钉在屏幕右上角)</div>

    <section class="pre-scroll">
      <p>先向下滚动,再观察 topbar 何时开始吸顶</p>
    </section>

    <header class="topbar">
      <div class="topbar-inner">
        <strong>Day4 Position 实战</strong>
        <nav>
          <a href="#intro">介绍</a>
          <a href="#absolute-relative">角标</a>
          <a href="#sticky-fixed">笔记</a>
        </nav>
      </div>
    </header>

    <main class="container">
      <section class="card" id="intro">
        <h1>Day4 Position 练习页面</h1>
        <p>本日目标:掌握 relative / absolute / fixed / sticky 的使用场景。</p>
      </section>
      <section  class="card badge-card" id="absolute-relative">
        <span class="badge">NEW</span>
        <h2>absolute + relative 角标示例</h2>
        <p>父元素用 <code>position: relative</code>,角标用 <code>position: absolute</code></p>
      </section>
      <section class="card" id="sticky-fixed">
        <h2>sticky + fixed 说明</h2>
        <p>顶部导航使用 <code>position: sticky</code>,滚动到顶部后保持可见。</p>
        <p>右下角按钮使用 <code>position: fixed</code>,始终固定在视口位置。</p
      </section>
      <section class="filler"></section>

      <a href="#intro" class="back-top">回到顶部</a>
    </main>
    
  </body>

</html>
❌
❌