普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月9日首页

Markdown 宽表格突破容器边界滚动方案

2026年1月9日 01:38

在聊天/文档类应用中,实现宽表格突破内容区域限制,利用更多屏幕空间进行水平滚动的技术方案。

背景与问题

在开发类似 ChatGPT、DeepSeek 等 AI 对话应用时,Markdown 渲染是核心功能之一。当用户或 AI 生成包含多列的宽表格时,会遇到一个常见问题:

内容区域通常有最大宽度限制(如 800px),以保证文字阅读体验。但宽表格在这个限制内显示时,要么被截断,要么需要在很小的区域内滚动,用户体验很差。

理想效果

观察 DeepSeek 等产品的实现,可以发现一个优雅的解决方案:

  1. 普通内容:保持在限宽区域内(如 800px)
  2. 窄表格:和普通内容一样左对齐,不做特殊处理
  3. 宽表格:突破内容区域限制,可以利用整个视口宽度进行滚动

image.png

技术挑战

挑战 1:overflow 冲突

最直观的想法是让表格容器突破父级宽度。但如果父级有垂直滚动(overflow-y: auto),根据 CSS 规范,overflow-x: visible 会被强制转为 auto,导致无法突破。

/* 这样不行! */
.chat-messages {
  overflow-y: auto;    /* 垂直滚动 */
  overflow-x: visible; /* 会被强制转为 auto */
}

挑战 2:负 margin 与居中布局

常见的居中方式是 margin: 0 auto,但这种方式下,子元素使用负 margin 无法有效突破。

挑战 3:表格初始位置对齐

如果表格容器扩展到整个视口宽度,表格会从视口最左边开始显示,而不是和内容区域对齐。

解决方案

核心思路

  1. 用 padding 代替 margin 实现居中:这样子元素可以用负 margin 突破 padding
  2. 条件性突破:只有宽表格才突破,窄表格正常显示
  3. 初始滚动位置:设置 scrollLeft 让表格初始位置对齐内容区域

布局结构设计

┌─────────────────────────────────────────────────────────────┐
│ .chat-page (100vw, overflow-x: hidden)                      │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ .chat-scroll-area (overflow-y: auto)                  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │ .chat-content (padding 居中,而非 margin)        │  │  │
│  │  │                                                 │  │  │
│  │  │   .message                                      │  │  │
│  │  │     └─ .table-breakout-wrapper (负 margin 突破)  │  │  │
│  │  │                                                 │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

实现代码

1. 容器布局(ChatBox.vue)

<template>
  <div class="chat-page">
    <div class="chat-scroll-area">
      <div class="chat-content">
        <ChatMessage v-for="msg in messages" :key="msg.id" :message="msg" />
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 页面容器 - 防止水平滚动条 */
.chat-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  width: 100vw;
  overflow-x: hidden;
}

/* 滚动区域 - 处理垂直滚动 */
.chat-scroll-area {
  flex: 1;
  width: 100vw;
  overflow-y: auto;
  overflow-x: hidden;
}

/* 关键:用 padding 居中,而不是 margin */
.chat-content {
  --content-max-width: 800px;
  --content-padding: max(16px, calc((100vw - var(--content-max-width)) / 2));
  width: 100%;
  padding-left: var(--content-padding);
  padding-right: var(--content-padding);
  box-sizing: border-box;
}
</style>

要点

  • .chat-content 使用 padding 而不是 margin: 0 auto 居中
  • 使用 CSS max() 函数确保小屏幕下有最小 padding
  • 父级 overflow-x: hidden 防止出现水平滚动条

2. 表格渲染(marked 自定义 renderer)

import { marked } from 'marked'

const renderer = new marked.Renderer()

renderer.table = function(table) {
  // 构建表格 HTML...
  const tableHtml = `<table>...</table>`

  // 包裹容器结构
  return `
    <div class="table-breakout-wrapper">
      <div class="table-scroll-box">
        <div class="table-scroll-content">${tableHtml}</div>
        <div class="table-scroll-gutter">
          <div class="table-scroll-bar"></div>
        </div>
      </div>
    </div>
  `
}

marked.use({ renderer })

3. 突破边界逻辑(核心 JS)

// 计算突破边界的偏移量
const calculateBreakoutOffsets = () => {
  const messageRect = messageRef.value.getBoundingClientRect()
  const viewportWidth = window.innerWidth
  const pagePadding = 16 // 保留边距

  return {
    // 消息区域左边到视口左边的距离
    leftOffset: Math.max(0, messageRect.left - pagePadding),
    // 视口右边到消息区域右边的距离
    rightOffset: Math.max(0, viewportWidth - messageRect.right - pagePadding)
  }
}

// 应用突破样式
const applyBreakoutStyles = (wrapper, content) => {
  const { leftOffset, rightOffset } = calculateBreakoutOffsets()

  // 获取表格实际宽度
  const table = content.querySelector('table')
  const tableWidth = table.scrollWidth
  const containerWidth = messageRef.value.getBoundingClientRect().width

  // 关键判断:表格没超出容器,不需要突破
  if (tableWidth <= containerWidth) {
    wrapper.style.marginLeft = ''
    wrapper.style.marginRight = ''
    content.scrollLeft = 0
    return
  }

  // 表格超出容器,应用突破样式
  wrapper.style.marginLeft = `-${leftOffset}px`
  wrapper.style.marginRight = `-${rightOffset}px`

  // 设置初始滚动位置,让表格左边对齐内容区域
  if (!wrapper.dataset.scrollInitialized) {
    wrapper.dataset.scrollInitialized = 'true'
    content.scrollLeft = leftOffset
  }
}

核心逻辑

  1. 条件判断tableWidth <= containerWidth 时不做任何处理
  2. 负 margin 突破marginLeft = -leftOffset 抵消父级的 padding-left
  3. 初始滚动位置scrollLeft = leftOffset 让表格视觉上对齐内容区域

4. 样式定义

/* 突破容器 */
.table-breakout-wrapper {
  position: relative;
  margin-top: 16px;
  margin-bottom: 16px;
  box-sizing: border-box;
}

/* 滚动内容区域 */
.table-scroll-content {
  overflow-x: auto;
  overflow-y: hidden;
  /* 隐藏原生滚动条 */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.table-scroll-content::-webkit-scrollbar {
  display: none;
}

/* 表格样式 */
table {
  border-collapse: collapse;
  width: max-content; /* 关键:宽度由内容决定 */
  font-size: 14px;
}

th, td {
  padding: 12px 16px;
  white-space: nowrap;
  border-bottom: 1px solid #e8e8e8;
}

要点

  • width: max-content 让表格宽度由内容决定,不会被压缩
  • white-space: nowrap 防止单元格内容换行

image.png

5. 自定义滚动条(可选)

const initScrollBar = (content, gutter, bar) => {
  const updateBar = () => {
    const scrollWidth = content.scrollWidth
    const clientWidth = content.clientWidth
    const maxScroll = scrollWidth - clientWidth

    if (scrollWidth <= clientWidth) {
      gutter.style.display = 'none'
      return
    }

    gutter.style.display = 'block'

    // 滚动条宽度
    const ratio = clientWidth / scrollWidth
    const barWidth = Math.max(clientWidth * ratio, 40)
    bar.style.width = barWidth + 'px'

    // 滚动条位置
    const maxBarLeft = clientWidth - barWidth
    const scrollRatio = maxScroll > 0 ? content.scrollLeft / maxScroll : 0
    bar.style.left = (scrollRatio * maxBarLeft) + 'px'
  }

  content.addEventListener('scroll', updateBar)
  window.addEventListener('resize', updateBar)
  updateBar()
}

原理图解

负 margin 突破原理

正常状态(margin 居中):
┌──────────────────────────────────────────┐
│          ┌────────────────┐              │
│  margincontent 800pxmargin      │
│          └────────────────┘              │
│          子元素无法突破 margin            │
└──────────────────────────────────────────┘

padding 居中 + 负 margin:
┌──────────────────────────────────────────┐
│ padding  ┌────────────────┐  padding     │
│ ←──────  │  content 800px │  ──────→     │
│          └────────────────┘              │
│                                          │
│ ┌────────────────────────────────────┐   │
│ │  子元素 margin-left: -padding       │   │
│ │  成功突破到视口边缘                   │   │
│ └────────────────────────────────────┘   │
└──────────────────────────────────────────┘

初始滚动位置对齐

容器突破后,表格从最左边开始:
│ leftOffset │    content    │ rightOffset │
│←──────────→│               │←───────────→│
┌────────────┬───────────────┬─────────────┐
│[表格从这开始...]                          │
└──────────────────────────────────────────┘
            ↑ 但我们希望表格从这里开始

设置 scrollLeft = leftOffset 后:
┌────────────┬───────────────┬─────────────┐
│  滚动隐藏   │[表格对齐这里]  │  可继续滚动  │
└────────────┴───────────────┴─────────────┘
             ↑ 视觉上对齐内容区域

关键技术点总结

技术点 说明
padding 居中 使用 padding 而非 margin: 0 auto,让子元素可以突破
负 margin 子元素 margin-left: -padding 突破到视口边缘
条件判断 只有 tableWidth > containerWidth 时才突破
scrollLeft 对齐 设置初始滚动位置让表格视觉上对齐内容区域
overflow-x: hidden 最外层容器防止出现水平滚动条
width: max-content 表格宽度由内容决定,不被压缩

兼容性

  • 现代浏览器完全支持
  • CSS max() 函数需要 Chrome 79+、Firefox 75+、Safari 11.1+
  • 可使用 calc() 配合媒体查询作为降级方案

应用场景

  • AI 对话应用(ChatGPT、Claude、DeepSeek 等)
  • 在线文档工具(Notion、语雀、飞书文档)
  • Markdown 编辑器/预览器
  • 任何需要展示宽表格的内容型应用

参考

  • CSS Overflow Module Level 3
  • CSS Box Model Module Level 3
  • marked.js 自定义渲染器文档

本方案在 Vue 3 + Vite + marked.js 环境下实现和测试。

告别全局污染:深入解析现代前端的模块化 CSS 演进之路

作者 San30
2026年1月8日 23:44

在前端开发的蛮荒时代,CSS(层叠样式表)就像一匹脱缰的野马。它的“层叠”特性既是强大的武器,也是无数 Bug 的根源。每个前端工程师可能都经历过这样的噩梦:当你为了修复一个按钮的样式而修改了 .btn 类,结果却发现隔壁页面的导航栏莫名其妙地崩了。

这就是全局命名空间污染

随着现代前端工程化的发展,React 和 Vue 等框架的兴起,组件化成为了主流。既然 HTML 和 JavaScript 都可以封装在组件里,为什么 CSS 还要流落在外,互相打架呢?今天,我们就结合实际代码,深入探讨前端界是如何通过模块化 CSS 来彻底解决“样式冲突”这一世纪难题的。

一、 从 Bug 说起:为什么我们需要模块化?

在传统的开发模式中,CSS 是没有“作用域”(Scope)概念的。所有的类名都暴露在全局环境下。

1.1 命名冲突的灾难

想象一下,在一个大型多人协作的项目中。

  • 开发 A 负责写一个通用的提交按钮,他给按钮起名叫 .button,设置了蓝底白字。
  • 开发 B 负责写一个侧边栏的开关按钮,他也随手起名叫 .button,设置了红底黑字。

当这两个组件被引入到同一个页面(App)时,CSS 的“层叠”规则(Cascading)就会生效。谁的样式在最后加载,或者谁的优先级(Specificity)更高,谁就会赢。结果就是:要么 A 的按钮变红了,要么 B 的按钮变蓝了。

1.2 传统的妥协:BEM 命名法

为了解决这个问题,以前我们发明了 BEM(Block Element Modifier)命名法,比如写成 .article__button--primary。这种方法虽然有效,但它本质上是靠开发者的自觉冗长的命名来模拟作用域。这并不是真正的技术约束,而是一种君子协定。

我们需要更硬核的手段:让工具帮我们生成独一无二的名字

二、 React 中的解决方案:CSS Modules

React 社区对于这个问题的标准答案之一是 CSS Modules。它的核心思想非常简单粗暴:既然人取名字会重复,那就让机器来取名字。

2.1 什么是 CSS Modules?

在你的项目中,你可能看到过后缀为 .module.css 的文件。这不仅仅是一个命名约定,更是构建工具(如 Webpack 或 Vite)识别 CSS Module 的标志。

让我们看一个实际的例子。假设我们需要两个不同的按钮组件:ButtonAnotherButton

Button.module.css:

.button {
    background-color: lightblue;
    color: black;
    padding: 10px 20px;
}

.txt {
    color: red;
}

AnotherButton.module.css:

.button {
    background-color: #008c8c;
    color: white;
    padding: 10px 20px;
}

请注意,这两个文件中都定义了 .button 类。在传统 CSS 中,这绝对会冲突。但在 CSS Modules 中,这两个 .button 是完全隔离的。

2.2 编译原理:哈希(Hash)魔法

当我们在 React 组件中引入这些文件时,并没有直接引入 CSS 字符串,而是引入了一个对象

Button.jsx:

// module.css 是 css module 的文件
// react 将 css 文件 编译成 js 对象
import styles from './Button.module.css';

console.log(styles); // 让我们看看这里打印了什么

export default function Button() {
    return (
        <>
            <h1 className={styles.txt}>你好,世界!!!</h1>
            <button className={styles.button}>My Button</button>
        </>
    )
}

如果你在浏览器控制台查看 console.log(styles),你会发现输出的是类似这样的对象:

{
  button: "Button_button__3a8f",
  txt: "Button_txt__5g9d"
}

核心机制:

  1. 编译转换:构建工具读取 CSS 文件,将类名作为 Key。
  2. 哈希生成:工具会根据文件名、类名和文件内容,生成一个唯一的 Hash 字符串(例如 3a8f),将其拼接成新的类名作为 Value。
  3. 替换引用:在 JSX 中,我们使用 {styles.button},实际上渲染到 HTML 上的是 <button class="Button_button__3a8f">

2.3 真正的样式隔离

现在我们再看看 AnotherButton.jsx

import styles from './antherButton.module.css';

export default function AnotherButton() {
    // 这里的 styles.button 对应的是完全不同的哈希值
    return <button className={styles.button}>Another Button</button>
}

App.jsx 中同时引入这两个组件:

import Button from './components/Button.jsx';
import AnotherButton from './components/AnotherButton.jsx';

export default function App() {
  return (
    <>
      {/* 这里的样式互不干扰,因为它们的最终类名完全不同 */}
      <Button />
      <AnotherButton />
    </>
  )
}

总结 CSS Modules 的优势:

  • 安全性:彻底杜绝了全局污染,每个组件的样式都是私有的。
  • 零冲突:多人协作时,你完全不需要担心你的类名和同事的重复。
  • 自动化:不需要人工维护复杂的 BEM 命名,构建工具自动处理。

三、 Vue 中的解决方案:Scoped CSS

Vue 采用了另一种更符合直觉的策略。Vue 的设计哲学是“单文件组件”(SFC),即 HTML、JS、CSS 全部写在一个 .vue 文件中。为了实现样式隔离,Vue 提供了 scoped 属性。

3.1 scoped 的工作原理

看看这个 HelloWorld.vue 组件:

<template>
  <h1 class="txt">你好,世界!!!</h1>
  <h2 class="txt2">一点点</h2>
</template>

<style scoped>
.txt {
  color: pink;
}
.txt2 {
  color: palevioletred;
}
</style>

当你给 <style> 标签加上 scoped 属性时,Vue 的编译器(通常是 vue-loader@vitejs/plugin-vue)会做两件事:

  1. HTML 标记:给模板中的每个 DOM 元素添加一个独一无二的自定义属性,通常以 data-v- 开头,例如 data-v-7ba5bd90
  2. CSS 重写:利用 CSS 的属性选择器,将样式规则重写。

编译后的 CSS 变成了这样:

.txt[data-v-7ba5bd90] {
  color: pink;
}
.txt2[data-v-7ba5bd90] {
  color: palevioletred;
}

编译后的 HTML 变成了这样:

<h1 class="txt" data-v-7ba5bd90>你好,世界!!!</h1>

3.2 样式穿透与父子组件

Vue 的 Scoped 样式有一个有趣的特性。看 App.vue 的例子:

<template>
<div>
  <h1 class="txt">Hello world in App</h1>
  <HelloWorld />
</div>
</template>

<style scoped>
.txt {
  color: #008c8c;
}
</style>

这里 App.vue 也有一个 .txt 类。但是,由于 App.vue 会生成一个不同的 data-v-hash ID,它的 CSS 选择器会变成 .txt[data-v-app-hash],而 HelloWorld 组件内部的 .txt 只有 .txt[data-v-helloworld-hash] 才能匹配。

这意味着:父组件的样式默认不会泄露给子组件,子组件的样式也不会影响父组件。

Vue Scoped 的优势:

  • 可读性好:类名在开发工具中依然保持原样(.txt),只是多了一个属性,调试起来比 CSS Modules 的乱码类名更友好。
  • 性能:只生成一次 Hash ID,利用浏览器原生的属性选择器,性能开销极低。
  • 开发体验:无需像 React 那样 import styles,直接写类名即可,符合传统 HTML/CSS 开发习惯。

四、 进阶玩法:CSS-in-JS (Styled-Components)

如果我们再激进一点呢?既然 JavaScript 统治了世界,为什么不把 CSS 也变成 JavaScript 的一部分?这就诞生了 CSS-in-JS,其中最著名的库就是 styled-components

这种方案在 React 社区非常流行,它将“组件”和“样式”彻底融合了。

4.1 万物皆组件

在提供的 APP.jsx (Styled-components 版本) 示例中,我们不再写 .css 文件,而是直接定义带样式的组件:

import styled from 'styled-components';

// 创建一个名为 Button 的样式组件
// 这是一个包含了样式的 React 组件
const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'white'};
  color: ${props => props.primary ? 'white' : 'blue'};
  border: 1px solid blue;
  padding: 8px 16px;
  border-radius: 4px;
`;

注意到了吗?这里的 CSS 是写在反引号(` `)里的,这在 ES6 中叫做标签模板字符串(Tagged Template Literals)

4.2 动态样式的威力

CSS Modules 和 Vue Scoped 虽然解决了作用域问题,但它们本质上还是静态的 CSS 文件。如果你想根据组件的状态(比如 primarydisabledactive)来改变样式,通常需要动态拼接类名。

但在 styled-components 中,CSS 变成了逻辑

background: ${props => props.primary ? 'blue' : 'white'};

这行代码意味着:如果在使用组件时传递了 primary 属性,背景就是蓝色,否则是白色。

function App() {
  return (
    <>
      <Button>默认按钮</Button>
      <Button primary>主要按钮</Button>
    </>
  )
}

当 React 渲染这两个按钮时,styled-components 会动态生成两个不同的 CSS 类名,并将对应的样式注入到页面的 <style> 标签中。

CSS-in-JS 的优势:

  • 动态性:样式可以像 JS 变量一样灵活,直接访问组件的 Props。
  • 删除无用代码:既然样式是绑定在组件上的,如果组件没被使用,样式也不会被打包。
  • 维护性:你永远不用去寻找“这个类名定义在哪里”,因为它就在组件的代码里。

五、 总结:如何选择?

在现代前端开发中,我们有多种武器来对抗样式冲突:

  1. CSS Modules (React 推荐)

    • 适用场景:大型 React 项目,团队习惯传统的 CSS/SCSS 编写方式,追求极致的性能(编译时处理)。
    • 特点:通过 Hash 类名实现隔离,输出 JS 对象。
    • 关键词.module.css, import styles, 安全, 零冲突。
  2. Vue Scoped Styles (Vue 默认)

    • 适用场景:绝大多数 Vue 项目。
    • 特点:通过 data-v- 属性选择器实现隔离,代码更简洁,可读性更高。
    • 关键词<style scoped>, 属性选择器, 简单易用。
  3. CSS-in-JS (Styled-components / Emotion)

    • 适用场景:需要高度动态主题、复杂的交互样式,或者团队偏好“All in JS”的 React 项目。
    • 特点:样式即逻辑,运行时生成 CSS。
    • 关键词styled.div, 动态 Props, 逻辑复用。

回到开头的问题:

不管是 CSS Modules 的哈希乱码,还是 Vue 的属性标记,或者是 Styled-components 的动态注入,它们的终极目标都是一样的——让样式为组件服务,而不是让组件去迁就样式。

在你的下一个项目中,请务必抛弃全局 CSS,拥抱模块化。这不仅是为了避免 Bug,更是为了写出更优雅、更健壮、更易于维护的代码。

希望这篇文章能帮你彻底理解前端样式的模块化演进! Happy Coding!

昨天 — 2026年1月8日首页

CSS 新特性!瀑布流布局的终极解决方案

作者 冴羽
2026年1月8日 17:11

前言

前端开发一直有一个老大难的问题,那就是——瀑布流布局。

效果需求并不复杂:卡片错落,参差有致,看起来高级,滚动起来流畅。

就是这样一个看似简单的效果,其实已经困扰了前端开发者好多年。

要引入 JavaScript 库,要让内容智能填充,要实现响应式布局,写无数个媒体查询,要实现无限滚动加载,要用 JavaScript 处理复杂的布局逻辑……

现在,经过 Mozilla、苹果 WebKit 团队、CSS 工作组和所有浏览器的多轮讨论,它终于有了终极解决方案!

这就是 CSS Grid Lanes

且让我们先翻译它为“CSS 网格车道”吧。

之所以叫车道,想象一下高速公路:有好几条车道,车辆会自动选择最短的那条车道排队。

CSS Grid Lanes 就是这个原理——你先定义好有几条“车道”(列),网页内容会自动填充到最短的那一列,就像车辆自动选择最不拥堵的车道一样。

具体使用起来也很简单,三行代码就能实现:

.container {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

实现原理

现在,让我们来细致讲解下如何实现开头图中的瀑布流效果。

首先是 HTML 代码:

<main class="container">
  <figure><img src="photo-1.jpg" /></figure>
  <figure><img src="photo-2.jpg" /></figure>
  <figure><img src="photo-3.jpg" /></figure>
  <!-- etc -->
</main>

然后是 CSS 代码:

.container {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

代码一共 3 行。

display: grid-lanes 创建网格容器,使用瀑布流布局。

grid-template-columns 创建车道,我们将值设为 repeat(auto-fill, minmax(250px, 1fr))意思是至少 250 像素宽的灵活列。浏览器决定创建多少列,并填充所有可用空间。

gap: 16px表示车道之间有 16px 的间歇。

就是这么简单。

3 行 CSS 代码,无需任何媒体查询或容器查询,我们就创建了一个适用于所有屏幕尺寸的灵活布局。


更绝的是,这种布局能让用户通过 Tab 键在各个栏目之间切换,访问所有当前可见的内容(而不是像以前那样,先滚动到第一列底部,然后再返回第二列顶部)。

它也支持你实现无限循环加载,随着用户滚动页面,内容无限加载,而无需使用 JavaScript 来处理布局。

功能强大

不同车道尺寸

Grid Lanes 充分利用了 CSS Grid 的强大功能 grid-template-*来定义车道,所以很容易创建出富有创意的布局。

例如,我们可以创建一个布局,其中窄列和宽列交替出现——即使列数随视口大小而变化,第一列和最后一列也始终是窄列。

实现也很简单:

grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr) minmax(16rem, 2fr)) minmax(8rem, 1fr);

效果如下:

跨车道

由于我们拥有网格布局的全部功能,我们当然也可以跨越车道。

效果如下:

实现代码:

main {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(20ch, 1fr));
  gap: 2lh;
}
article {
  grid-column: span 1;
}
@media (1250px < width) {
  article:nth-child(1) {
    grid-column: span 4;
  }
  article:nth-child(2),
  article:nth-child(3),
  article:nth-child(4),
  article:nth-child(5),
  article:nth-child(6),
  article:nth-child(7),
  article:nth-child(8) {
    grid-column: span 2;
  }
}

放置项目

我们也可以在使用网格车道时显式地放置项目。这时,无论有多少列,标题始终位于最后一列。

实现代码为:

main {
  display: grid-lanes;
  grid-template-columns: repeat(auto-fill, minmax(24ch, 1fr));
}
header {
  grid-column: -3 / -1;
}

改变方向

网格车道也可以双向排列!

上面的所有示例创建的是“瀑布式”布局,内容以列的形式排列。

网格车道也可以用于创建另一种方向的布局,即“砖块式”布局。

当使用 grid-template-columns定义列时,浏览器会自动创建瀑布式布局,如下所示:

.container {
  display: grid-lanes;
  grid-template-columns: 1fr 1fr 1fr 1fr;
}

如果你想要反方向的砖块布局,使用 grid-template-rows

.container {
  display: grid-lanes;
  grid-template-rows: 1fr 1fr 1fr;
}

容差

“容差”是为 Grid Lanes 创建的一个新概念。它允许你调整布局算法在决定放置项目位置时的精确度。

回到高速公路的比喻:

假设 1 号车道前面的车比 2 号车道长了 1 厘米,下一辆车要排到哪条车道?

如果严格按“哪条短选哪条”,它会选 2 号车道。但 1 厘米的差距根本不重要!这样来回切换车道反而让人困惑。

“容差”就是告诉系统:“差距小于这个值,就当作一样长”。

容差默认值是 1em(大约一个字的高度)。

为什么容差很重要呢?

因为用键盘 Tab 键浏览网页的人(比如视障用户)会按内容顺序跳转。

如果布局乱跳,他们会很迷惑。合适的容差能让浏览体验更流畅。

现在能用吗?

目前可以在 Safari 技术预览版 234 中体验,其他浏览器还在开发中。

苹果 WebKit 团队从 2022 年中就开始实现这个功能,现在基本语法已经稳定了。虽然还有些细节在讨论(比如属性命名),但核心用法不会变。

你可以访问 webkit.org/demos/grid3 看各种实际例子。

最后

欢迎围观我的“网页版朋友圈”,关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。

昨天以前首页

查看 Base64 编码的字体包对应的字符集

2026年1月7日 14:39

Base64 转换为 字体文件 .woff

若从代码中看到Base64 编码的字体包,不好确认包含哪些字体,可以通过截取的方式进行解析:

@font-face {
    font-family: 'SourceHanSerifCN-Regular';
    src: url('data:font/woff2;charset=utf-8;base64,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') format('woff2'),
        url('data:font/woff;charset=utf-8;base64,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') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

截取Base64的部分

@font-face {
  font-family: 'MyFont';
  src: url(data:font/woff2;base64,你的base64字符串) format('woff2');
  font-weight: normal;
  font-style: normal;
}

截取后通过JS解析(可以直接在浏览器端运行)

// 解码 Base64 为二进制数据
function base64ToFont(base64String, filename = 'font.woff') {
  // 移除 data URL 前缀(如果有)
  const base64Data = base64String.replace(/^data:font\/\w+;base64,/, '');
  
  // 解码 Base64
  const binaryString = atob(base64Data);
  const bytes = new Uint8Array(binaryString.length);
  
  for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  
  // 创建 Blob 对象
  const blob = new Blob([bytes], { type: 'font/woff' });
  
  // 创建下载链接
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  link.click();
  
  // 清理
  URL.revokeObjectURL(url);
}

比如上述的文件

base64ToFont('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')

解析后浏览器自动下载了 font.woff 文件

image.png

查看字体包的 Character set

Wakamai Fondue: Drop A Font ! 只需要把 font.woff 文件传上去,即可查看字体文件对应的字符集(如图)

SourceHanSerifCN-Regular from 1.0 css.png

从图中就可以查看到对应的字符集包含哪些内容,更清晰了解到哪些字体不在字体包内。

字符集说明

需要注意一点,字符集对应的编码是【十六进制】

如上图所看到的字符 【A】 对应的编码是 【FF21】 (说明是个特殊字符)

这跟我们平时键盘敲出来的字符A是不同的,键盘 【A】 的是 【0x41】 (如下图所示)

image.png

要确认清楚字符集的对应,这样才能更好地了解清楚,避免错认。

大写字母A-Z编码

字符 十进制 十六进制 二进制
A 65 0x41 01000001
B 66 0x42 01000010
C 67 0x43 01000011
D 68 0x44 01000100
E 69 0x45 01000101
F 70 0x46 01000110
G 71 0x47 01000111
H 72 0x48 01001000
I 73 0x49 01001001
J 74 0x4A 01001010
K 75 0x4B 01001011
L 76 0x4C 01001100
M 77 0x4D 01001101
N 78 0x4E 01001110
O 79 0x4F 01001111
P 80 0x50 01010000
Q 81 0x51 01010001
R 82 0x52 01010010
S 83 0x53 01010011
T 84 0x54 01010100
U 85 0x55 01010101
V 86 0x56 01010110
W 87 0x57 01010111
X 88 0x58 01011000
Y 89 0x59 01011001
Z 90 0x5A 01011010

小写字母a-z编码

字符 十进制 十六进制 二进制
a 97 0x61 01100001
b 98 0x62 01100010
c 99 0x63 01100011
d 100 0x64 01100100
e 101 0x65 01100101
f 102 0x66 01100110
g 103 0x67 01100111
h 104 0x68 01101000
i 105 0x69 01101001
j 106 0x6A 01101010
k 107 0x6B 01101011
l 108 0x6C 01101100
m 109 0x6D 01101101
n 110 0x6E 01101110
o 111 0x6F 01101111
p 112 0x70 01110000
q 113 0x71 01110001
r 114 0x72 01110010
s 115 0x73 01110011
t 116 0x74 01110100
u 117 0x75 01110101
v 118 0x76 01110110
w 119 0x77 01110111
x 120 0x78 01111000
y 121 0x79 01111001
z 122 0x7A 01111010

0-9的数字编码如下

字符 十进制 十六进制 二进制
0 48 0x30 00110000
1 49 0x31 00110001
2 50 0x32 00110010
3 51 0x33 00110011
4 52 0x34 00110100
5 53 0x35 00110101
6 54 0x36 00110110
7 55 0x37 00110111
8 56 0x38 00111000
9 57 0x39 00111001

CSS终于支持渐变色的过渡了🎉

作者 JIE_
2026年1月6日 10:33

背景

在做项目时,总会遇到UI给出渐变色的卡片或者按钮,但在做高亮的时候,由于没有过渡,显得尤为生硬。

过去的解决方案

在过去,我们如果要实现渐变色的过渡,通常会使用如下几种方法:

  1. 添加遮罩层,通过改变遮罩层的透明度做出淡入淡出的效果,实现过渡。
  2. 通过background-size/position使得渐变色移动,实现渐变色移动的效果。
  3. 通过filter: hue-rotate滤镜实现色相旋转,实现过渡。

但这几种方式都有各自的局限性:

  1. 遮罩层的方式看似平滑,但不是真正的过渡,差点意思。
  2. background-size/position的方式需要计算好background-sizebackground-position,否则会出现渐变不完整的情况。并且只是实现了渐变的移动,而不是过渡。
  3. filter: hue-rotate也需要计算好旋转角度,实现复杂度高,过渡的也不自然。

@property新规则

@property规则可以定义一个自定义属性,并且可以指定该属性的语法、是否继承、初始值等。

@property --color {
  syntax: '<color>';
  inherits: false;
  initial-value: #000000;
}

我们只需要把这个自定义属性--color应用到linear-gradient中,在特定的时候改变它的值,非常轻松就可以实现渐变色的过渡了。 我们再看看@property规则中这些属性的含义。

Syntax语法描述符

Syntax用于描述自定义属性的数据类型,必填项,常见值包括:

  • <number> 数字(如0,1,2.5)
  • <percentage> 百分比(如0%,50%,100%)
  • <length> 长度单位(如px,em,rem)
  • <color> 颜色值
  • <angle> 角度值(如deg,rad)
  • <time> 时间值(如s,ms)
  • <image> 图片
  • <*> 任意类型

Inherits继承描述符

Inherits用于描述自定义属性是否从父元素继承值,必填项:

  • true 从父元素继承值
  • false 不继承,每个元素独立

Initial-value初始值描述符

Initial-value用于描述自定义属性的初始值,在Syntax为通用时为可选。

兼容性

@property目前仍是实验性规则,但主流浏览器较新版本都已支持。 b70bdd98-15d5-4aa3-a3c4-b4d08a7aba9c.png

总结与展望

@property规则的出现,标志着CSS在动态样式控制方面迈出了重要一步。它不仅解决了渐变色过渡的技术难题,更为未来的CSS动画和交互设计开辟了新的可能性。 随着浏览器支持的不断完善,我们可以期待:

  • 更丰富的动画效果
  • 更简洁的代码实现
  • 更好的性能表现
❌
❌