布局和配色这两件事有个共同特点:靠脑内推演效率极低,靠改一行刷一次页面也慢。属性之间互相影响,参数取值又是连续的,最快的方式是把参数摆在面板上直接拖,看着结果反推规则。
这篇讲的是「拖之前需要先明白的那几条规则」,避免调出一个看着对但换个内容就塌的布局。所有面板都在浏览器里实时渲染,不需要建项目、不需要跑构建,改动只发生在你自己的页面上。
一、Flex 解决一维排列,Grid 解决二维网格
选错这一步,后面所有属性都会用得别扭。
- Flex 是一维的。它只管一个方向上的排列:主轴方向排开,交叉轴方向对齐。子项尺寸由内容和
flex属性协商决定,容器不预先规定格子。适合导航条、按钮组、卡片内部的图文排列、以及任何「按内容多少自然撑开」的场景。 - Grid 是二维的。先声明行和列构成的网格,再把子项放进格子。行列尺寸由容器统一规定,子项可以跨行跨列。适合整页框架、仪表盘、商品列表这类「要求横竖都对齐」的场景。
一个判断口诀:如果你发现自己在用嵌套的 Flex 容器去模拟对齐的行列,就该换 Grid;如果你在用 Grid 却只声明了一行或一列,Flex 会更简单。
Flex与Grid布局生成可以两种模式来回切,容器属性调完点击预览区任意方块就能选中该子项单独设置 flex-grow、flex-shrink、flex-basis、align-self、order,Grid 模式下还能设置跨行跨列。子项数量可随时增减,专门用来验证换行与自动填充行为。
三个必须知道的细节,它们是「预览里好看、真实内容一进来就变形」的主要来源:
- **
1fr的最小值默认是auto**,所以一个包含长单词或长 URL 的子项会把轨道撑宽,看起来就不等分了。真正的等分要写minmax(0, 1fr)。这是 Grid 最常见的一个坑。 auto-fill与auto-fit的区别只在没填满时显现。auto-fill保留空轨道,剩余空间被空格子占着;auto-fit会把空轨道折叠掉,让现有子项撑满。只有三张卡片却想让它们铺满一行,用auto-fit。flex: 1与flex-grow: 1不等价。前者是简写,同时把flex-basis设成了0,意味着完全忽略内容宽度按比例分配;后者保留flex-basis: auto,内容多的子项起点就更宽。两栏「等宽」调不出来,多半是这里。
二、居中与自适应的标准写法
居中的写法太多,实际值得记住的就这几条。
单个元素在容器内水平垂直居中,Flex 三行搞定,display: flex 加 justify-content: center 加 align-items: center。Grid 更短:
display: grid;
place-items: center;
两端对齐、中间自动留白,用 justify-content: space-between。注意 space-around 与 space-evenly 的差别:前者每个子项左右各分一半间距,因此首尾的外侧间距是中间间距的一半;后者所有间隙完全相等。设计稿要求首尾贴边就用 space-between,要求间距视觉均匀就用 space-evenly。
间距不要再用子项的 margin 拼。gap 同时被 Flex 和 Grid 支持,它只作用于子项之间,不会在容器边缘多出一份间距,也不用写「最后一项去掉 margin」这种补丁。
一行放不下就换行的自适应卡片列表,Grid 一行声明即可:grid-template-columns: repeat(auto-fit, minmax(240px, 1fr))。它的含义是「每列至少 240px,能放几列放几列,剩余空间平分」,不需要任何媒体查询就能从手机排到宽屏。这是最值得记住的一条 Grid 写法。
垂直方向的「主内容撑满、页脚吸底」,容器 display: flex 加 flex-direction: column 加 min-height: 100vh,主内容区加 flex: 1。比用绝对定位或 calc 减去页脚高度可靠得多。
视觉细节则交给CSS渐变阴影圆角生成。渐变支持 linear、radial、conic 三种类型及其 repeating 形式,色标最多八个、各自可调颜色位置与透明度;阴影可叠加多层,box-shadow 与 text-shadow 都支持,还能切 inset 内阴影或改用 filter 的 drop-shadow 给不规则图形加影。两条经验:多层阴影是层次感的来源,通常「一层小模糊贴边 + 一层大模糊扩散」最自然;单层阴影的模糊半径超过 24px 通常只会让元素显得发灰而不是立体。预览区带棋盘格底纹,判断半透明叠加效果时靠它。conic-gradient 在较老浏览器上不支持,用于关键视觉时准备一个回退纯色。
三、Hex、RGB、HSL 互转的精度问题
三种表示法不是同一个空间的不同写法,转换过程存在真实的信息损失。
- Hex 与 RGB 之间是无损的。
#FF5733就是rgb(255, 87, 51),两者一一对应,来回转多少次都不变。三位缩写#F53等价于#FF5533,注意它是每位重复而不是补零。 - RGB 与 HSL 之间会丢精度。RGB 每通道是 0 到 255 的整数,HSL 的色相是 0 到 360 度、饱和度和明度是百分比,转换涉及浮点运算与四舍五入。往返一次后
#3498DB可能变成#3498DA,肉眼看不出但代码里比对字符串会不相等。所以设计规范里请固定一种表示法作为唯一真值,另一种只在临时调色时用。 - 灰色的色相是不确定的。饱和度为 0 时色相没有意义,转换实现通常返回 0,于是「灰色的色相是红色」这种看起来荒谬的结果是正常的。给灰色调整色相不会有任何视觉变化,除非同时给一点饱和度。
- 透明度是独立的第四个通道。
rgba和hsla的 alpha 不参与色相饱和度明度的换算,八位 Hex 的后两位同样是 alpha。半透明色叠在不同背景上呈现完全不同的实际颜色,这一点在算对比度时尤其要注意。
颜色代码转换支持这三种格式互转,输入任意一种即可拿到其他表示。日常用途很明确:设计稿给的是 Hex,而调整明暗、生成同色系的深浅阶梯必须在 HSL 里做,因为只有 HSL 把「颜色本身」和「多亮」分成了两个可独立调节的维度。
四、WCAG 的 4.5:1 与 3:1 到底在说什么
对比度是一个比值,由两个颜色的相对亮度算出:(L1 + 0.05) / (L2 + 0.05),其中 L 是按 sRGB 分量做伽马校正后加权求和的相对亮度。取值范围从 1:1(两色相同)到 21:1(纯黑与纯白)。
它衡量的是明暗差异,不是色彩差异。这带来一个反直觉但重要的结论:红配绿的对比度可能很低。两个色相截然不同但亮度接近的颜色,在数值上不达标,在色觉障碍用户眼里也确实难以区分。所以「看起来很跳」不等于「可读」。
WCAG 2.x 的几档要求各有适用范围:
- AA 级正文 4.5:1。这是绝大多数产品应当满足的基线,也是无障碍法规通常引用的档位。
- AA 级大号文字 3:1。大号文字指 18pt 常规或 14pt 粗体以上,换算成 CSS 像素约为 24px 常规、18.5px 粗体。别把 16px 的正文按大号标准放宽,这是最常见的自我欺骗。
- AAA 级正文 7:1、AAA 级大号文字 4.5:1。要求更严,通常用于面向老年用户或视力障碍用户的产品。
- 非文本元素 3:1。图标、表单输入框边框、焦点指示环也在要求范围内。浅灰边框的输入框在白底上往往只有 1.5:1,用户根本看不出哪里可以输入——这是实际项目里最容易漏掉的一项。
颜色对比度检查会一次给出上面五行判定,每行明确通过或不通过,并用 12px、16px、24px 三种字号以及按钮、边框的真实样式做预览。只看数字容易忽略观感,只看观感容易高估自己的眼睛,两者都要看。计算在本地完成,配色方案不会被上传或统计。
五、改色的正确方向是只动明度
对比度不达标时,最省事也最错误的做法是把文字改成纯黑或纯白。这会让品牌色消失,一个页面改几处就变得毫无个性。
正确方向是在 HSL 空间里保持色相与饱和度不变,只沿明度方向调整,并且朝远离背景亮度的那一侧走:浅色背景上把文字压暗,深色背景上把文字提亮。这样调出来的颜色仍然是原设计的同一个色调,只是深浅不同,放在色板里是自然的一阶。工具的「自动修正」正是按这个策略实现的,它在 HSL 空间沿明度逼近 4.5:1,优先朝远离背景的方向调,所以结果不会退化成黑白。
配套的几条实践:
- 先固定背景,再调前景。背景通常承担更多设计约束(品牌色块、图片),前景文字色是更廉价的调整对象。
- 深色模式不是把颜色取反。用「交换」功能快速检查反色方案的对比度,通常需要单独一套明度更高、饱和度略低的前景色,直接反转会得到刺眼的高饱和色。
- 文字压在图片或渐变上时按最不利区域取色。整张图的平均亮度没有意义,取文字实际覆盖到的最亮(或最暗)局部去测。稳妥做法是加一层半透明遮罩或文字阴影,把局部亮度拉到可控范围。
- 不要只靠颜色传递信息。表单错误只标红、图表只用颜色区分系列,对色觉障碍用户等于没有信息。补一个图标、一段文字或不同的线型。
- 禁用态是唯一的例外。WCAG 对禁用控件不作对比度要求,但也别做到完全看不见,否则用户会以为页面坏了。
常见问题
Grid 的列宽明明写了 1fr 却不等分,是浏览器 bug 吗?
不是。1fr 的最小尺寸默认为 auto,内容更宽的子项会把轨道撑开。改成 minmax(0, 1fr) 即可强制等分,代价是过长的内容会溢出,需要配合 overflow 或 word-break 处理。
Flex 的子项为什么被压得比内容还窄?
flex-shrink 默认为 1,空间不足时所有子项按比例收缩,而收缩的下限受 min-width 影响,默认 auto 但在某些嵌套情况下会失效。给不希望被压缩的子项设 flex-shrink: 0,或显式给一个 min-width。
同一个颜色在设计稿和浏览器里看着不一样? 先排除三件事:设计稿的色彩配置文件是否为 sRGB、显示器是否开启了广色域或护眼模式、以及元素上是否叠了半透明层或混合模式。CSS 的颜色值本身没有歧义,差异来自渲染链路。
对比度算出来达标,为什么用户还是说看不清? 对比度只覆盖明暗一个维度。字号过小、字重过细、行高过密、使用了极细的西文字体,都会在数值达标的情况下依然难读。数值是底线不是目标,最终要在真实字号下用眼睛确认。