阅读视图

发现新文章,点击刷新页面。

JS-面试必考:手动实现一个标准的 Ajax 请求(XMLHttpRequest)

前言

虽然 fetch API 在现代开发中非常流行,但 XMLHttpRequest (XHR) 依然是理解 Web 网络编程的基石。无论是底层的 Ajax 封装,还是需要细粒度监控上传/下载进度的场景,XHR 依然不可替代。

一、 什么是 Ajax?

Ajax(Asynchronous JavaScript And XML) 并不是一种单一的编程语言,而是一套技术的组合。其核心是在不重新加载整个网页的情况下,通过与服务器进行异步数据交换,实现页面的局部刷新

  • 核心优势:提升用户体验,减少网络带宽占用。
  • 实现基础:JavaScript 和浏览器原生的 XMLHttpRequest 对象。

二、 XHR 操作的“五步法”逻辑

1. 创建对象

let xhr = new XMLHttpRequest();

2. 注册回调函数 (事件驱动)

XHR 是基于事件驱动的,我们需要在请求发送前定义好如何处理各种状态。

  • onreadystatechange:监控请求的生命周期(状态 0-4)。
  • onerror:处理网络层面的错误(如断网或 DNS 解析失败)。
  • ontimeout:处理响应超时,需配合 xhr.timeout 使用。
  • onprogress:数据传输进度监控。

3. 配置请求参数

通过 open() 方法初始化请求:

  • method:请求方法(GET, POST, PUT 等)。
  • url:请求地址。
  • async:是否异步(默认 true)。注意: 设为 false 会阻塞浏览器主线程,导致页面假死。

4. 设置属性与请求头

  • responseType:设置期望的返回格式(如 json, blob)。
  • setRequestHeader():手动添加自定义请求头(必须在 open 之后,send 之前调用)。

5. 发送请求

  • send(data):正式发起请求。如果是 GET 请求,传 null;如果是 POST,传具体数据。

三、 详解 readyState:五种状态的演变

readyState 是排查 Ajax 问题的关键,它记录了请求的每一步进展:

取值 状态名称 描述
0 UNSENT 初始化状态,对象已创建,尚未调用 open()
1 OPENED 已调用 open(),尚未调用 send(),可设置 Header
2 HEADERS_RECEIVED 已调用 send(),接收到了响应头和状态码
3 LOADING 正在下载响应体,responseText 已包含部分数据
4 DONE 数据接收完成,请求生命周期结束

四、 进度监控:如何实现进度条?

XHR 相比 fetch 的一大优势就是对进度的原生支持。通过 onprogress 事件,我们可以计算出下载百分比:

  • lengthComputable:布尔值,表示服务器是否返回了 Content-Length
  • loaded:已接收的字节数。
  • total:总字节数。

五、 代码实现:封装一个标准的 Ajax 函数

/**
 * 基于 XHR 封装的数据获取函数
 * @param {string} URL 请求地址
 */
function GetWebData(URL) {
  const xhr = new XMLHttpRequest();

  // 1. 状态监控:只在 DONE 阶段处理逻辑
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) { 
      // 兼容性判断:2xx 范围和 304 缓存均认为成功
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        console.log("请求成功:", xhr.response);
      } else {
        console.error("请求失败, 状态码:", xhr.status);
      }
    }
  };

  // 2. 进度监控
  xhr.onprogress = function (event) {
    if (event.lengthComputable) {
      let percent = (event.loaded / event.total) * 100;
      console.log(`下载进度: ${percent.toFixed(2)}%`);
    }
  };

  // 3. 异常与超时处理
  xhr.timeout = 5000; 
  xhr.ontimeout = () => console.error("请求超时!");
  xhr.onerror = () => console.error("网络异常或跨域错误");

  // 4. 配置与发送
  xhr.open("GET", URL, true);
  xhr.responseType = "json"; // 自动解析 JSON
  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

  xhr.send(null);
}

HTTP-深度拆解 UDP 与 TCP 的核心差异

前言

在网络传输层,TCP 像是一位严谨的“快递员”,确保包裹万无一失;而 UDP 更像是一位“投递员”,只管快速把信件投进邮筒。在 HTTP/3.0(QUIC)全面转向 UDP 的今天,重新理解这两个协议的底层逻辑显得尤为重要。

一、 UDP:为了速度而生的“轻骑兵”

1. 核心概念

UDP(User Datagram Protocol)提供的是无连接、不可靠的传输服务。它不关心对方是否收到,只负责把报文“推”出去。

2. UDP为什么轻量级?

TCP 的首部至少 20 字节,而 UDP 只有固定的 8 字节,它没有复杂的序列号和窗口调节,这意味着 CPU 处理 UDP 包的速度远高于 TCP。这 8 个字节被平分为四个字段,每个字段 2 字节:

  1. 源端口号 (Source Port) :发送端的端口,在不需要对方回信时可全为 0。
  2. 目的端口号 (Destination Port) :接收端的端口,必须指定。
  3. 长度 (Length) :UDP 报文段的长度(包含首部和数据),最小值为 8。
  4. 校验和 (Checksum) :检测 UDP 报文在传输中是否有错,有错就丢弃。

2. 核心特点

  • 无连接时延:发送数据前不需要三次握手,拿起就发,响应极快。
  • 无拥塞控制:即便网络环境恶劣,UDP 也会保持恒定的发送速率(所以直播才会有“卡顿”而不是“暂停”)。
  • 面向报文:应用层给多少,它就传多少。不合并、不拆分,保留了报文的边界。
  • 低开销首部:首部仅占 8 字节,相比 TCP 的 20 字节起步,传输效率极高。
  • 灵活的多播性:天然支持一对一、一对多、多对多的交互通信。

3. 应用场景

  • 实时性要求高:视频会议、语音通话、在线直播。
  • 数据量小且频繁:DNS 查询(一次往返即结束)。
  • 新兴协议基础:HTTP/3.0 (QUIC 协议)。

二、 TCP 与 UDP:全维度对比

为了方便记忆,我们可以通过下表快速对比两者的差异:

维度 TCP (传输控制协议) UDP (用户数据报协议)
连接性 面向连接(需三次握手) 无连接(即发即收)
可靠性 可靠(保证不丢、不重、有序) 不可靠(尽力而为,不保结果)
传输效率 较慢(因各种控制机制) 极快(协议简单,实时性强)
资源消耗 较高 较低
通信模式 仅限点对点(1对1) 支持单播、多播、广播
首部开销 20 ~ 60 字节 固定 8 字节
数据流控制 流量控制、拥塞控制

三、 如何选择?(深度思考)

  • 选 TCP 的理由

    当你需要确保数据的完整性时。比如:发送一封邮件、下载一个安装包、或者是浏览网页。如果中间丢了一个字符,整个文件可能就失效了。

  • 选 UDP 的理由

    当你的应用场景对延迟敏感,且能容忍少量数据丢失时。比如:直播里少了一帧画面,人眼几乎察觉不到,但如果为了等这一帧而导致视频卡住 2 秒,用户体验会极差。

HTTP-深入浅出 TCP 协议:从报文结构到三次握手与四次挥手

前言

在计算机网络中,TCP (Transmission Control Protocol) 就像是一位极其负责的“快递员”。它不追求极速(那是 UDP 的事),但它保证每一份数据都能不丢、不重、按序地送达目的地。

一、 TCP 核心特性:为什么它如此可靠?

  • 面向连接:通信前必须“点对点”建立连接。
  • 可靠传输:通过序列号、确认应答、超时重传机制实现无差错传输。
  • 全双工通信:连接建立后,双方可以同时发送和接收数据。
  • 面向字节流:数据被视为无结构的字节流,由 TCP 根据窗口大小进行分段。

二、 读懂 TCP 报文:那些关键的“信号灯”

在理解握手之前,必须先看懂报文首部中的核心字段:

1. 序列号与确认

  • Sequence Number (seq) :报文段发送的第一个字节的序号,保证了数据的有序性
  • Acknowledgement Number (ack) :指期望收到对方下一个报文段的第一个字节序号,代表此前的序号已成功接收。

2. 控制位(标志符)

这些 1 bit 的标志位决定了报文的“性格”:

  • SYN=1:请求建立连接。
  • ACK=1:确认号有效。TCP 规定连接建立后所有报文 ACK 必须置 1。
  • FIN=1:请求释放连接。
  • RST=1:连接出现严重问题,强制重置。
  • PSH=1:尽快交付应用层,不要等缓冲区满。
  • URG=1:紧急数据优先处理。

三、 三次握手:如何安全地“握住”对方?

三次握手的核心目的:确认双方的收发能力均正常,并同步初始序列号。

  1. 第一次握手:客户端发送 SYN=1, seq=x。客户端进入 SYN-SENT 状态。

  2. 第二次握手:服务端返回 SYN=1, ACK=1, seq=y, ack=x+1。服务端进入 SYN-RECEIVED 状态。

  3. 第三次握手:客户端返回 ACK=1, seq=x+1, ack=y+1

    • 关键点:双方此后均进入 ESTABLISHED(已建立连接)状态。

image.png

💡 深度思考:为什么不是两次?

为了防止“已失效的连接请求”突然到达服务端导致资源浪费。

想象一下:。客户端发送了⼀个连接请求 A,但是因为⽹络原因造成了超时,这时 TCP 会启动超时重传的机制再次发送⼀个连接请求 B。此时请求顺利到达服务端,服务端应答完就建⽴了请求。如果连接请求 A 在两端关闭后终于抵达了服务端,那么这时服务端会认为客户端⼜需要建⽴ TCP 连接,从⽽应答了该请求并进⼊ ESTABLISHED 状态。此时客户端其实是 CLOSED 状 态,那么就会导致服务端⼀直等待,造成资源的浪费。


四、 四次挥手:体面的“分手”流程

TCP 连接是双向的,因此断开连接时,每一端都要单独关闭。

  1. 第一次挥手:主动方发送 FIN=1,表示“我不再发数据了”。
  2. 第二次挥手:被动方返回 ACK=1。此时连接处于半关闭状态,被动方可能还有没发完的数据要继续传。
  3. 第三次挥手:被动方发送完数据后,发送 FIN=1,表示“我也可以关了”。
  4. 第四次挥手:主动方返回 ACK=1。经过 2*MSL 时间后,连接彻底关闭。

💡 深度思考:为什么挥手比握手多一次?

  • 握手时:服务端可以把 SYN(连接请求)和 ACK(确认)放在一个包里发回去。
  • 挥手时:当服务端收到 FIN 时,它可能还有数据没发完。所以它先回一个 ACK 表示收到了,等数据发完了才发自己的 FIN这就导致了 ACKFIN 分开发送。

五、 总结:TCP 与流量控制

除了握手挥手,TCP 还通过 Window Size(窗口大小) 实现了流量控制:

  • 接收端根据自己的处理能力告诉发送端:“我还能收多少”。
  • 这样可以防止发送端发得太快,导致接收端缓冲区溢出。

HTTP-协议溯源:深度解析 HTTP/3.0 与 QUIC 协议的底层革命

前言

HTTP/2 开启了多路复用的时代,但它依然跑在 TCP 这条“老旧”的铁轨上。只要 TCP 的设计初衷(顺序传输、字节流可靠性)不改变,队头阻塞(HOL Blocking) 的幽灵就永远无法彻底散去。于是,Google 另辟蹊径,基于 UDP 打造了 QUIC,并最终成为了 HTTP/3 的核心。

一、 HTTP/2 的遗憾:TCP 层的队头阻塞

虽然 HTTP/2 过多路复用解决了 HTTP 在应用层 的队头阻塞,使应用层发生的请求不再需要排队等候,解决了 HTTP/1.1 的队头阻塞。,但由于它底层依然依赖 TCP,产生了一个新的瓶颈

  • 传输层队头阻塞问题 :HTTP/2 建立在 TCP 之上。TCP 要求字节必须按顺序交付。如果网络发生丢包,TCP 无法分辨哪些字节属于哪个请求,只能暂停整个连接等待重传。

  • 弱网环境下的尴尬:在丢包率较高的移动网络下,HTTP/2 的表现有时甚至不如开启 6 个连接的 HTTP/1.1,因为“一荣俱荣,一损俱损”。

  1. 连接建立的开销:TCP 三次握手 + TLS 握手,在弱网环境下 RTT(往返时延)非常高。

image.png

image.png


二、 HTTP/3 的逆袭:基于 UDP 的 QUIC 协议

由于 TCP 协议深植于操作系统内核和路由器固件中,修改极其困难。HTTP/3 索性“推倒重来”,在 UDP 之上实现了可靠传输,这便是 QUIC(Quick UDP Internet Connections)

1. QUIC 的四大核心魔法

① 彻底解决队头阻塞

QUIC 在传输层实现了多路复用。每个流(Stream)之间是相互独立的。

  • 效果:如果 Stream A 丢包,只会阻塞 Stream A 的重组,Stream B 和 Stream C 可以继续不受影响地传输数据

② 极速握手(0-RTT / 1-RTT)

TCP 需要先建立连接再建立加密通道。

  • 效果:QUIC 将传输握手加密握手合并。初次连接只需 1 个 RTT,再次连接甚至可以实现 0-RTT 极速启动。

③ 连接迁移 (Connection Migration)

TCP 依靠“四元组”(源IP、源端口、目标IP、目标端口)识别连接。

  • 痛点:当你从 Wi-Fi 切换到 4G,IP 变了,TCP 连接必断。
  • 改进:QUIC 使用 Connection ID 识别连接。即便 IP 切换,只要 ID 不变,连接就能无缝延续。

④ 改进的头部压缩:QPACK

HTTP/2 使用的 HPACK 在乱序传输时会产生队头阻塞,HTTP/3 升级为 QPACK,专门优化了多流并发下的压缩效率。


三、 总结:从“排队”到“并发”

特性 HTTP/2 (TCP) HTTP/3 (UDP/QUIC)
传输层阻塞 存在 TCP 队头阻塞 不存在(流与流独立)
握手时延 2~3 RTT (TCP+TLS) 0~1 RTT
网络切换 连接断开,需重新握手 无缝迁移
可靠性 操作系统内核实现 QUIC 在应用层实现

四、 HTTP3.0现状

目前HTTP3.0并没有普及,你可以随便打开网站查看请求相应的HTTP版本,会发现大多数请求都是HTTP2.0的!

JS-原生实战:手把手带你封装一个高性能模态框(Modal)组件

前言

在网页开发中,弹框(Modal)是最常见的交互组件之一。一个合格的弹框不仅要能“跳出来”,还需要处理好遮罩层、层级关系(z-index)、防滚动穿透以及用户便捷退出的体验。本文将从零开始带你实现一个具备丝滑动画效果的模态框。

一、 核心设计思路

实现弹框主要分为三个部分,它们的层级关系如下:

  1. 遮罩层 (Overlay) :铺满全屏的半透明背景,用于阻断用户对背景页面的操作,并突出弹框,初始diasplaynone,并使用position: fixed使其铺满屏幕。

  2. 内容盒 (Content) :位于遮罩层之上,水平垂直居中,承载具体信息,初始diasplaynone

  3. 控制逻辑

    • 打开:显示遮罩与弹框,同时锁定背景滚动,将遮罩层与内容盒的display属性均设置为block,并设置弹框内容盒子的z-index让其比遮罩层高。
    • 关闭:隐藏元素,恢复背景滚动。支持点击“关闭按钮”、点击“遮罩层”以及按下 “Esc 键”退出。

二、 关键技术点解析

1. 为什么使用 position: fixed 而非 absolute

在遮罩层和弹框的样式中,我们优先选择 fixed

  • absolute 是相对于最近的已定位祖先元素,如果页面很长,滚动后弹框可能会“飞走”。
  • fixed 是相对于浏览器视口(Viewport)定位,无论页面如何滚动,弹框始终保持在屏幕中央。

2. 完美的水平垂直居中

利用 top: 50%left: 50% 将左上角定位到中心,再配合 transform: translate(-50%, -50%) 将盒子向左和向上平移自身宽高的 50%,实现真正的精准居中。

3. 防止“滚动穿透”

当弹框打开时,用户依然可以滚动背景页面,这在用户体验上是不好的。

  • 解决方案:打开弹框时设置 document.body.style.overflow = "hidden"

三、 完整代码实现

以下是优化后的代码,加入了细腻的淡入(FadeIn)和滑入(SlideIn)动画。

<!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>
      body {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }

      /* 背景遮罩层 */
      .modal-overlay {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.6);
        z-index: 999;
        animation: fadeIn 0.3s ease-out;
      }

      /* 弹框样式 */
      .modal {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 50%;
        max-width: 500px;
        background-color: white;
        border-radius: 16px;
        z-index: 1000;
        padding: 32px;
        animation: slideIn 0.4s ease-out;
        overflow: hidden;
      }
      /* 动画效果 */
      @keyframes fadeIn {
        from {
          opacity: 0;
        }
        to {
          opacity: 1;
        }
      }

      /* 淡入动画 */
      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translate(-50%, -60%);
        }
        to {
          opacity: 1;
          transform: translate(-50%, -50%);
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <header>
        <h1>弹框组件演示</h1>
      </header>

      <section class="content-section">
        <button class="open-btn" id="openModalBtn">打开弹框</button>
      </section>
      <section style="height: 1000px;"></section>
    </div>

    <!-- 灰色背景遮罩层 -->
    <div class="modal-overlay" id="modalOverlay"></div>

    <!-- 弹框 -->
    <div class="modal" id="modal">
      <h2>欢迎使用弹框组件</h2>
      <button id="closeModalBtn">关闭弹框</button>
    </div>

    <script>
      // 获取DOM元素
      const openModalBtn = document.getElementById("openModalBtn"); //打开按钮
      const modalOverlay = document.getElementById("modalOverlay"); //遮罩层
      const modal = document.getElementById("modal"); //弹框
      const closeModalBtn = document.getElementById("closeModalBtn"); //关闭按钮

      // 打开弹框
      function openModal() {
        modalOverlay.style.display = "block";
        modal.style.display = "block";
        document.body.style.overflow = "hidden"; // 防止背景滚动
      }

      // 关闭弹框
      function closeModal() {
        modalOverlay.style.display = "none";
        modal.style.display = "none";
        document.body.style.overflow = "auto"; // 恢复滚动
      }

      // 给打开按钮绑定打开弹框操作
      openModalBtn.addEventListener("click", openModal);

      // 点击背景遮罩层关闭弹框
      modalOverlay.addEventListener("click", closeModal);

      // 按ESC键关闭弹框
      document.addEventListener("keydown", function (event) {
        if (event.key === "Escape" && modal.style.display === "block") {
          closeModal();
        }
      });

    </script>
  </body>
</html>

CSS -实战技巧:彻底搞定单行、多行文本溢出省略号显示

前言

在 UI 布局中,为了保证页面的整洁,我们经常需要对超出容器范围的文本进行截断并显示省略号(...)。针对不同的布局需求和浏览器兼容性,通常有三种主要的实现方案。

一、 单行文本溢出省略

这是最常用、兼容性最好的方案,适用于标题、标签等场景。

核心代码:

.overflow-text {
  width: 200px;           /* 必须设置宽度 */
  white-space: nowrap;    /* 1. 强制文字不换行 */
  overflow: hidden;       /* 2. 隐藏超出部分 */
  text-overflow: ellipsis; /* 3. 使用省略号代替被截断文字 */
}

二、 多行文本溢出省略(WebKit 内核方案)

这是目前移动端和现代浏览器(Chrome, Safari, Edge)最常用的方案。它利用了 WebKit 的私有属性,实现简单且效果精准。

核心代码:

.multi-overflow {
  display: -webkit-box;           /* 1. 必须结合 box 模型使用 */
  -webkit-box-orient: vertical;   /* 2. 设置伸缩盒子的子元素排列方式 */
  -webkit-line-clamp: 2;          /* 3. 限制显示的行数 */
  overflow: hidden;               /* 4. 隐藏超出范围的内容 */
}

注意:该属性虽然是 WebKit 私有,但目前主流浏览器支持度已经非常高,是实际开发的首选。


三、 自定义多行省略(伪元素方案)

如果你需要兼容一些不支持 line-clamp 的旧版浏览器,或者需要更灵活的自定义样式,可以使用伪元素模拟。

核心代码:

.demo {
  position: relative;     /* 为伪元素提供定位参照 */
  line-height: 20px;      /* 行高 */
  height: 40px;           /* 高度必须是行高的整数倍(此处为2行) */
  overflow: hidden;       /* 隐藏多余内容 */
  word-break: break-all;
}

.demo::after {
  content: "...";         /* 插入省略号 */
  position: absolute;
  bottom: 0;
  right: 0;
  padding-left: 10px;
  background: white;      /* 关键:背景色需与容器背景一致,遮盖末尾文字 */
}

方案优缺点:

  • 优点:兼容性极佳。
  • 缺点:即使文字没超出行数,省略号也会一直显示(可通过 JS 判断高度动态添加类名来解决)。

四、 总结与最佳实践

需求场景 推荐方案 关键词
单行文本 标准 CSS 方案 nowrap + ellipsis
移动端/现代浏览器 WebKit 方案 -webkit-line-clamp
极端兼容性要求 伪元素方案 ::after + 绝对定位

CSS-动画进阶:从 @keyframes 到 Animation 属性全解析

前言

相比传统的 transition(过渡),animation(动画)提供了更强大的控制能力。它不需要触发条件,可以自动播放,并且通过“关键帧”能够实现极其复杂的视觉效果。

一、 动画的“两步走”战略

实现一个 CSS 动画通常分为两步:

  1. 定义动画:使用 @keyframes 规定不同时间点的样式状态。
  2. 调用动画:在目标元素上使用 animation 属性。

二、 Animation 属性详解(简写语法)

为了提高效率,我们通常使用简写方式:

animation: name duration timing-function delay iteration-count direction fill-mode;

1. 核心属性表

属性 描述 默认值
animation-name 关键帧动画的名称
animation-duration 动画周期时长(必须带单位,如 2s 0
animation-timing-function 速度曲线(平滑度控制) ease
animation-delay 动画延迟启动时间 0
animation-iteration-count 播放次数(数字或 infinite 1
animation-direction 是否反向播放(如 alternate normal
animation-fill-mode 动画结束后的状态(如 forwards 停在终点) none

2. 关键属性深度解析

  • animation-timing-function (速度曲线)

    • ease:默认值,先慢后快再慢。
    • linear:匀速运动,适合背景滚动或旋转。
    • steps(n):逐帧动画,适合做像素画或 Loading 效果。
  • animation-iteration-count (循环次数)

    • n:播放 nn 次。
    • infinite:无限循环播放。

三、 实战案例:方块平移旋转

需求:方块在 3s 内匀速向右平移400px并旋转360°,无限循环。

.card {
  width: 100px;
  height: 100px;
  background: #ff4757;
  /* 针对 width 和 transform 设置过渡 */
  animation: moves 3s ease infinite;
}

@keyframes moves {
  0% {
    transform: translate(0,0) rotate(0deg);
  }
  100% {
    transform: translate(400px,0px) rotate(360deg);
  }
}

四、 进阶技巧与避坑指南

  1. 性能优化(优先使用 Transform)

    在动画中修改 margin-leftleft 会触发浏览器的重排 (Reflow) ,消耗大量性能。推荐使用 transform: translateX() ,它通过 GPU 加速,动画更加丝滑。

  2. 动画状态保持

    如果你想让动画播完后停留在最后一帧,请加上 forwardsanimation-fill-mode)。

  3. 单位不能省

    animation-duration 即使是 0 秒也必须带单位 0s,否则部分浏览器会判定为语法错误导致动画不生效。

CSS-深度解析 Position 五大定位模式

前言

在 CSS 布局中,position 属性是控制元素“脱离常规”的关键。无论是想要悬浮的导航栏,还是精准重叠的图层,都离不开对定位属性的深入理解。本文将带你搞懂 relativeabsolutefixedsticky 的底层逻辑。

一、 Position 核心参数详解

属性值 含义 布局表现 参考物
static 默认值 正常文档流布局。 无(Top/Left 等属性无效)。
relative 相对定位 不脱离文档流,占据原始空间。 元素在文档流中的原始位置
absolute 绝对定位 完全脱离文档流,不占位。 最近的 非 static 祖先元素。
fixed 固定定位 完全脱离文档流,不占位。 浏览器窗口 (Viewport)
sticky 粘性定位 混合模式,特定条件下生效。 最近的滚动祖先元素

二、 偏移属性:Left、Right、Top、Bottom

1. 生效前提

这些偏移属性仅在 position 不为 static 时生效。它们定义了元素边缘相对于参考物的距离。

2. 不同定位下的偏移逻辑

  • Relative (相对自身)

    设置 left: 20px,元素会相对于它原本在文档流中的位置向右移动 20px。它原来的位置依然被保留,不会被后续元素顶替。

  • Absolute (相对祖先)

    设置 left: 0,元素会紧贴最近的已定位(非 static)祖先元素的左内边缘。

  • Fixed (相对窗口)

    设置 right: 10px; bottom: 10px;,元素将永久停留在浏览器窗口的右下角,不随页面滚动而移动。


三、 现代布局黑科技:Sticky (粘性定位)

  • 表现:它是 relativefixed 的结合体。

  • 示例top: 0; position: sticky;

    当页面未滚动到该元素时,它是 relative(随内容滚动);当元素滚动到视口顶部时,它会像 fixed 一样“粘”在顶部,直到其父容器消失在视口中。

  • 常用场景:表格标题行固定、侧边栏跟随。


四、 核心面试考点:Absolute 的参考物

误区修正:很多人认为绝对定位是相对于“父元素”。

准确定义:是相对于最近的、非 static 定位的祖先元素。如果向上找遍了所有祖先元素都没有定位,则相对于 初始包含块(通常是 <html> 定位。


五、 总结与最佳实践

  1. 子绝父相:这是最经典的用法。父元素设为 relative(仅为了提供参考坐标),子元素设为 absolute 实现精准定位。
  2. 避免滥用 Fixed:固定定位会脱离文档流,过多的固定元素会遮挡用户视线,且在移动端可能存在兼容性坑位。
  3. 层级管理:配合 z-index 使用,数值越大,图层越靠上(仅在已定位元素上生效)。

CSS-布局基石:深度解析四大定位方式与文档流机制

前言

在 CSS 的世界里,页面布局的本质就是控制元素在“流”中的位置。理解普通流、浮动、绝对定位与固定定位,是掌握复杂页面结构的钥匙。本文将带你理清它们的底层逻辑与相互影响。

一、 普通流 (Normal Flow)

普通流(也叫文档流)是页面最默认的布局方式。

  • 布局逻辑:元素按照其在 HTML 中出现的先后顺序,自上而下、自左而右排列。

  • 元素表现

    • 块级元素 (Block) :独占一行,垂直排列。
    • 行内元素 (Inline) :水平排列,遇到边缘自动换行。
  • 地位:它是所有布局的基础,除非显式更改,否则元素都在流中。


二、 浮动 (Float)

浮动最初是为了实现“文字环绕图片”的效果,但后来演变成了主流的列布局工具。

  • 布局逻辑:元素首先处于普通流位置,然后向左或向右偏移,直到碰到包含框或另一个浮动元素的边缘。
  • 脱离文档流:浮动元素会部分脱离文档流。它不再占据普通流的空间,但依然会影响文档流中文字的排列(形成环绕)。
  • 注意:使用浮动后务必记得清除浮动 (Clearfix) ,否则会导致父元素高度坍塌。

三、 绝对定位 (Absolute Positioning)

绝对定位让元素拥有了“自由移动”的能力。

  • 布局逻辑:元素完全脱离文档流,不再占据物理空间,兄弟元素会像它不存在一样向上位移。

  • 定位参考(包含块)

    • 相对于其最近的非 static 定位的祖先元素进行定位。
    • 如果找不到,则相对于初始包含块(通常是 <html>)定位。
  • 常用技巧:父级 relative,子级 absolute


四、 固定定位 (Fixed Positioning)

固定定位是特殊的绝对定位,它让元素在屏幕上“静止”。

  • 布局逻辑:元素完全脱离文档流
  • 定位参考:相对于浏览器视口 (Viewport) 进行定位。
  • 特点:无论页面如何滚动,元素始终保持在屏幕的固定位置。常用于导航栏、回到顶部按钮或侧边广告。

五、 核心对比:脱离文档流情况

这是面试中最常被问到的对比点:

布局方式 是否脱离文档流 占据空间 相对参考物
普通流 兄弟元素与父元素
浮动 部分脱离 否(仅对文本有影响) 父元素边缘
绝对定位 完全脱离 最近的非 static 祖先元素
固定定位 完全脱离 浏览器视口

💡 总结建议

  • 普通流是常态;
  • 浮动处理简单的横向排列(虽然现代开发更多使用 Flex);
  • 绝对定位处理元素重叠和局部精准定位;
  • 固定定位处理与窗口挂钩的悬浮组件。

CSS-玩转背景控制与文字排版艺术

前言

在前端开发中,细节决定质感。如何让背景图只在内容区域显示?如何优雅地处理文字溢出?本文将带你深入探索 CSS 中关于 backgroundText 的进阶属性,让你的页面更加精致。

一、 背景属性的高级控制

1. background-clip(背景裁剪)

决定背景延伸到什么位置

  • border-box (默认):背景延伸至边框外沿(但在边框下层)。
  • padding-box:背景延伸至内边距外沿,不会显示在边框下。
  • content-box:背景只在内容区域显示。
  • text (特殊):将背景裁剪为文字的前景色(常用于制作渐变文字)。

2. background-origin(背景原点)

决定背景图片的绘制起点(即 background-position: 0 0 的位置)。

  • padding-box (默认):图片左上角对齐 padding 边缘。
  • border-box:图片左上角对齐 border 边缘。
  • content-box:图片左上角对齐内容区域边缘。

3. background-size(背景尺寸)

  • contain:保持比例缩放,确保图片完整显示在容器内(可能会有留白)。
  • cover:保持比例缩放,确保图片完全覆盖容器(可能会被裁剪)。
  • 具体数值:如 100px 50%,手动指定宽高。

4. box-decoration-break(元素断裂处的修饰)

当元素被分为几个独立的盒子(如使内联元素span跨越多行),background-break属性用来控制背景怎样在这些不同的盒子中显示,取值如下:

  • slice (默认):背景像是在一个整体上绘制后被切开,断裂处没有边框/内边距。
  • clone:每个断裂的盒子都拥有独立的背景、边框和内边距。

二、 文字排版与换行控制

1. 换行与溢出

  • word-wrap / overflow-wrap

    • normal:浏览器默认。
    • break-word:如果单词太长无法在一行显示,允许在单词内换行(更常用)。
  • word-break: break-all:强制在单词内任何字符间断行,适合处理超长连续字符。

2. text-overflow(文本溢出处理)

常用于处理单行文本超出容器:

  • clip:简单裁剪,不显示省略号。
  • ellipsis:显示省略号 ...(需配合 overflow: hiddenwhite-space: nowrap 使用)。

三、 text-decoration:文字装饰的简写艺术

现代 CSS 支持更精细的文字装饰控制: text-decoration: <line> <color> <style>

1. 线型 (Line)

  • none:去除装饰(最常用于 <a> 标签)。
  • underline:下划线。
  • overline:上划线。
  • line-through:删除线。

2. 样式 (Style)

  • solid:实线(默认)。
  • double:双线。
  • dotted:点线。
  • dashed:虚线。
  • wavy波浪线

四、 实战 tips:渐变文字效果

利用 background-clip: text,你可以轻松实现绚丽的渐变文字:

.gradient-text {
  background: linear-gradient(to right, red, blue);
  -webkit-background-clip: text; /* 必须加前缀 */
  color: transparent;           /* 文字设为透明,露出背景 */
  font-size: 40px;
}
❌