普通视图
CSS 特殊符号 / 英文导致换行问题速查表
Markdown 宽表格突破容器边界滚动方案
在聊天/文档类应用中,实现宽表格突破内容区域限制,利用更多屏幕空间进行水平滚动的技术方案。
背景与问题
在开发类似 ChatGPT、DeepSeek 等 AI 对话应用时,Markdown 渲染是核心功能之一。当用户或 AI 生成包含多列的宽表格时,会遇到一个常见问题:
内容区域通常有最大宽度限制(如 800px),以保证文字阅读体验。但宽表格在这个限制内显示时,要么被截断,要么需要在很小的区域内滚动,用户体验很差。
理想效果
观察 DeepSeek 等产品的实现,可以发现一个优雅的解决方案:
- 普通内容:保持在限宽区域内(如 800px)
- 窄表格:和普通内容一样左对齐,不做特殊处理
- 宽表格:突破内容区域限制,可以利用整个视口宽度进行滚动
![]()
技术挑战
挑战 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:表格初始位置对齐
如果表格容器扩展到整个视口宽度,表格会从视口最左边开始显示,而不是和内容区域对齐。
解决方案
核心思路
- 用 padding 代替 margin 实现居中:这样子元素可以用负 margin 突破 padding
- 条件性突破:只有宽表格才突破,窄表格正常显示
-
初始滚动位置:设置
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
}
}
核心逻辑:
-
条件判断:
tableWidth <= containerWidth时不做任何处理 -
负 margin 突破:
marginLeft = -leftOffset抵消父级的 padding-left -
初始滚动位置:
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防止单元格内容换行
![]()
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 居中):
┌──────────────────────────────────────────┐
│ ┌────────────────┐ │
│ margin │ content 800px │ margin │
│ └────────────────┘ │
│ 子元素无法突破 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 演进之路
在前端开发的蛮荒时代,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 的标志。
让我们看一个实际的例子。假设我们需要两个不同的按钮组件:Button 和 AnotherButton。
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"
}
核心机制:
- 编译转换:构建工具读取 CSS 文件,将类名作为 Key。
-
哈希生成:工具会根据文件名、类名和文件内容,生成一个唯一的 Hash 字符串(例如
3a8f),将其拼接成新的类名作为 Value。 -
替换引用:在 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)会做两件事:
-
HTML 标记:给模板中的每个 DOM 元素添加一个独一无二的自定义属性,通常以
data-v-开头,例如data-v-7ba5bd90。 - 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 文件。如果你想根据组件的状态(比如 primary、disabled、active)来改变样式,通常需要动态拼接类名。
但在 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。
- 删除无用代码:既然样式是绑定在组件上的,如果组件没被使用,样式也不会被打包。
- 维护性:你永远不用去寻找“这个类名定义在哪里”,因为它就在组件的代码里。
五、 总结:如何选择?
在现代前端开发中,我们有多种武器来对抗样式冲突:
-
CSS Modules (React 推荐)
- 适用场景:大型 React 项目,团队习惯传统的 CSS/SCSS 编写方式,追求极致的性能(编译时处理)。
- 特点:通过 Hash 类名实现隔离,输出 JS 对象。
-
关键词:
.module.css,import styles, 安全, 零冲突。
-
Vue Scoped Styles (Vue 默认)
- 适用场景:绝大多数 Vue 项目。
-
特点:通过
data-v-属性选择器实现隔离,代码更简洁,可读性更高。 -
关键词:
<style scoped>, 属性选择器, 简单易用。
-
CSS-in-JS (Styled-components / Emotion)
- 适用场景:需要高度动态主题、复杂的交互样式,或者团队偏好“All in JS”的 React 项目。
- 特点:样式即逻辑,运行时生成 CSS。
-
关键词:
styled.div, 动态 Props, 逻辑复用。
回到开头的问题:
不管是 CSS Modules 的哈希乱码,还是 Vue 的属性标记,或者是 Styled-components 的动态注入,它们的终极目标都是一样的——让样式为组件服务,而不是让组件去迁就样式。
在你的下一个项目中,请务必抛弃全局 CSS,拥抱模块化。这不仅是为了避免 Bug,更是为了写出更优雅、更健壮、更易于维护的代码。
希望这篇文章能帮你彻底理解前端样式的模块化演进! Happy Coding!
CSS 新特性!瀑布流布局的终极解决方案
前言
前端开发一直有一个老大难的问题,那就是——瀑布流布局。
效果需求并不复杂:卡片错落,参差有致,看起来高级,滚动起来流畅。
![]()
就是这样一个看似简单的效果,其实已经困扰了前端开发者好多年。
要引入 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 干货。
Flexbox 布局中的滚动失效问题:为什么需要 `min-h-0`?
查看 Base64 编码的字体包对应的字符集
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 文件
![]()
查看字体包的 Character set
Wakamai Fondue: Drop A Font ! 只需要把 font.woff 文件传上去,即可查看字体文件对应的字符集(如图)
![]()
从图中就可以查看到对应的字符集包含哪些内容,更清晰了解到哪些字体不在字体包内。
字符集说明
需要注意一点,字符集对应的编码是【十六进制】
如上图所看到的字符 【A】 对应的编码是 【FF21】 (说明是个特殊字符)
这跟我们平时键盘敲出来的字符A是不同的,键盘 【A】 的是 【0x41】 (如下图所示)
![]()
要确认清楚字符集的对应,这样才能更好地了解清楚,避免错认。
大写字母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终于支持渐变色的过渡了🎉
背景
在做项目时,总会遇到UI给出渐变色的卡片或者按钮,但在做高亮的时候,由于没有过渡,显得尤为生硬。
过去的解决方案
在过去,我们如果要实现渐变色的过渡,通常会使用如下几种方法:
- 添加遮罩层,通过改变遮罩层的透明度做出淡入淡出的效果,实现过渡。
- 通过
background-size/position使得渐变色移动,实现渐变色移动的效果。 - 通过
filter: hue-rotate滤镜实现色相旋转,实现过渡。
但这几种方式都有各自的局限性:
- 遮罩层的方式看似平滑,但不是真正的过渡,差点意思。
-
background-size/position的方式需要计算好background-size和background-position,否则会出现渐变不完整的情况。并且只是实现了渐变的移动,而不是过渡。 -
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目前仍是实验性规则,但主流浏览器较新版本都已支持。
![]()
总结与展望
@property规则的出现,标志着CSS在动态样式控制方面迈出了重要一步。它不仅解决了渐变色过渡的技术难题,更为未来的CSS动画和交互设计开辟了新的可能性。
随着浏览器支持的不断完善,我们可以期待:
- 更丰富的动画效果
- 更简洁的代码实现
- 更好的性能表现