CSS Protips
一份帮助你提升 CSS 技能的小技巧集。每个技巧都是一个独立的模式——根据你面临的问题,选用合适的那个。
使用 CSS 重置
CSS 重置有助于在不同浏览器之间强制执行样式一致性,为元素样式提供一个干净的起点。你可以找到许多重置模式,或者使用更简化的重置方法:
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
现在,元素将被去掉外边距和内边距,并且 box-sizing 让你能够使用 CSS 盒模型来管理布局。
演示
[!TIP] 如果你遵循下面的 继承
box-sizing技巧,你可能会选择不在 CSS 重置中包含box-sizing属性。
继承 box-sizing
让 box-sizing 从 html 继承:
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
这使得在利用其他行为的插件或其他组件中更改 box-sizing 更加容易。
演示
谨慎使用 all: unset 进行组件重置
当重置元素的属性时,你可以逐个重置每个属性:
button {
background: none;
border: none;
color: inherit;
font: inherit;
outline: none;
padding: 0;
}
或者使用 all 简写配合 unset。all: unset 会重置几乎所有属性(除了 unicode-bidi、direction 和自定义属性)——继承的属性恢复为继承值,其他属性恢复为初始值。<button> 会变为 display: inline 并失去其原生盒子,因此重置后务必恢复布局和焦点。
button.reset {
all: unset;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
button.reset:focus-visible {
outline: 2px solid currentColor;
outline-offset: 0.15em;
}
[!TIP] 如果要恢复用户代理样式表而不是空白状态,请使用
all: revert——参见现代 CSS 部分的 重置意图:unset与revert。
使用 :not() 在导航上应用/去除边框
与其先加边框……
/* 添加边框 */
.nav li {
border-right: 1px solid #666;
}
……然后再从最后一个元素上去掉它……
/* 移除边框 */
.nav li:last-child {
border-right: none;
}
……不如使用 :not() 伪类只应用于你想要的元素:
.nav li:not(:last-child) {
border-right: 1px solid #666;
}
这里,CSS 选择器的读取方式就和人类描述它一样。
演示
检查字体是否本地安装
你可以在远程获取字体之前检查字体是否已本地安装,这也是一条很好的性能优化技巧。
@font-face {
font-family: "Dank Mono";
src:
/* 完整名称 */ local("Dank Mono"), /* Postscript 名称 */ local("Dank Mono"),
/* 否则,下载它! */ url("//...a.server/fonts/DankMono.woff");
}
code {
font-family: "Dank Mono", system-ui-monospace;
}
感谢 Adam Argyle 分享这条技巧及演示。
将 line-height 添加到 body
你不需要为每个 <p>、<h*> 等单独添加 line-height。相反,将其添加到 body:
body {
line-height: 1.5;
}
这样文本元素可以轻松地从 body 继承。
演示
为表单元素设置 :focus
有视力的键盘用户依赖焦点来确定键盘事件在页面中的位置。使表单元素的焦点比浏览器的默认实现更突出和一致:
a:focus,
button:focus,
input:focus,
select:focus,
textarea:focus {
box-shadow: none;
outline: #000 dotted 2px;
outline-offset: 0.05em;
}
演示
[!TIP] 在现代代码中,优先使用
:focus-visible,这样键盘用户能保持清晰的焦点环,而不会在每次鼠标点击时显示焦点样式。
垂直居中任意元素
在移动端,优先使用**网格(Grid)**配合动态视口单位——100vh 映射到大视口,在浏览器工具栏显示/隐藏时可能表现异常。使用逻辑属性时,用 100dvb(动态视口块)。
.center-page {
min-block-size: 100dvb;
display: grid;
place-items: center;
}
一旦父元素有定义的块尺寸,Flexbox 也能以相同方式工作:
html,
body {
min-block-size: 100%;
}
body {
align-items: center;
display: flex;
justify-content: center;
}
[!NOTE] 传统模式:
height: 100vh+place-items: center在较旧的教程中仍然出现。对于移动端的全视口布局,优先使用dvh/dvb/svh/lvh——参见 MDN 上关于视口相对长度的内容。
[!TIP] 想要居中其他东西?CSS-Tricks 有一篇很好的文章 介绍如何做到这一切。
演示
使用 aspect-ratio 代替高度/宽度
aspect-ratio 属性允许你轻松调整元素大小并保持一致的宽高比。这在响应式网页设计中非常有用,可以防止布局偏移。结合 object-fit 使用,可以避免在图片高度/宽度值改变时破坏布局。
img {
aspect-ratio: 16 / 9; /* 宽度 / 高度 */
object-fit: cover;
}
在这篇 web.dev 文章中了解更多关于 aspect-ratio 属性的信息。
演示
逗号分隔的列表
使列表项看起来像真正的、逗号分隔的列表:
ul > li:not(:last-child)::after {
content: ",";
}
使用 :not() 伪类,最后一个项目将不会添加逗号。
[!NOTE] 此技巧可能不适用于无障碍性,特别是屏幕阅读器。而且,从浏览器复制/粘贴不适用于 CSS 生成的内容。请谨慎使用。
使用负 nth-child 选择项目
使用 CSS 中的负 nth-child 来选择第 1 到第 n 个项目。
li {
display: none;
}
/* 选择第 1 到第 3 个项目并显示它们 */
li:nth-child(-n + 3) {
display: block;
}
或者,既然你已经了解了一些关于使用 :not() 的知识,尝试一下:
/* 选择除前 3 个之外的所有项目并显示它们 */
li:not(:nth-child(-n + 3)) {
display: block;
}
演示
使用 SVG 作为图标
没有理由不使用 SVG 作为图标:
.logo {
background: url("logo.svg");
}
SVG 可以很好地适应所有分辨率类型,并且所有浏览器都支持它,甚至可以追溯到 IE9。抛弃你的 .png、.jpg 或 .gif-jif-whatever 文件吧。
[!NOTE] 如果你有为有视力用户设计的 SVG 图标按钮,但 SVG 加载失败,这将有助于保持无障碍性:
.no-svg .icon-only::after {
content: attr(aria-label);
}
使用“猫头鹰”选择器
它的名字可能很奇怪,但使用通用选择器 (*) 配合相邻兄弟选择器 (+) 可以提供强大的 CSS 能力:
* + * {
margin-top: 1.5em;
}
在这个例子中,文档流中所有跟在其他元素后面的元素都将获得 margin-top: 1.5em。
[!TIP] 想了解更多关于“猫头鹰”选择器的信息,请阅读 Heydon Pickering 在 A List Apart 上的文章。
演示
使用 max-height 实现纯 CSS 滑块
使用 max-height 配合 overflow: hidden 实现纯 CSS 滑块:
.slider {
max-height: 200px;
overflow-y: hidden;
width: 300px;
}
.slider:hover {
max-height: 600px;
overflow-y: scroll;
}
悬停时,元素会扩展到 max-height 值,并且由于溢出,滑块会显示出来。
等宽表格单元格
处理表格可能会很痛苦。尝试使用 table-layout: fixed 来保持单元格等宽:
.calendar {
table-layout: fixed;
}
轻松实现表格布局。
演示
使用 Flexbox 摆脱外边距黑科技
当处理列间隙时,你可以通过使用 flexbox 的 space-between 属性摆脱 nth-、first- 和 last-child 的黑科技:
.list {
display: flex;
justify-content: space-between;
}
.list .person {
flex-basis: 23%;
}
现在列间隙始终均匀分布。
对空链接使用属性选择器
当 <a> 元素没有文本值但 href 属性有链接时,显示链接:
a[href^="http"]:empty::before {
content: attr(href);
}
这非常方便。
演示
[!NOTE] 此技巧可能不适用于无障碍性,特别是屏幕阅读器。而且,从浏览器复制/粘贴不适用于 CSS 生成的内容。请谨慎使用。
使用 :is() 更好地控制特异性
:is() 伪类用于同时定位多个选择器,减少冗余并提高代码可读性。这对于以更紧凑的形式编写大型选择器非常有用。
:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
color: green;
}
上面的规则集等同于以下多个选择器规则……
section h1,
section h2,
section h3,
section h4,
section h5,
section h6,
article h1,
article h2,
article h3,
article h4,
article h5,
article h6,
aside h1,
aside h2,
aside h3,
aside h4,
aside h5,
aside h6,
nav h1,
nav h2,
nav h3,
nav h4,
nav h5,
nav h6 {
color: green;
}
演示
样式化“默认”链接
为“默认”链接添加样式:
a[href]:not([class]) {
color: #008000;
text-decoration: underline;
}
这样,通过 CMS 插入的链接(通常没有 class 属性)将具有区分度,而不会一般性地影响层叠。
固有比例盒子(旧版回退)
[!NOTE] 仅限旧版。 优先使用
aspect-ratio(Baseline Widely available)用于新代码。仅当必须支持非常老的浏览器或需要早于aspect-ratio的模式时,才保留此内边距技巧。
要创建一个具有固有比例的盒子,对 div 应用顶部或底部内边距:
.container {
height: 0;
padding-bottom: 20%;
position: relative;
}
.container div {
border: 2px dashed #ddd;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
使用 20% 的内边距会使盒子的高度等于其宽度的 20%。无论视口宽度如何,子 div 都将保持其宽高比(100% / 20% = 5:1)。
演示
样式化损坏的图片
通过一点 CSS 让损坏的图片在视觉上更令人愉悦:
img {
display: block;
font-family: sans-serif;
font-weight: 300;
height: auto;
line-height: 2;
position: relative;
text-align: center;
width: 100%;
}
现在添加伪元素规则来显示用户消息和损坏图片的 URL 引用:
img::before {
content: "很抱歉,下面的图片损坏了 :(";
display: block;
margin-bottom: 10px;
}
img::after {
content: "(url: " attr(src) ")";
display: block;
font-size: 12px;
}
[!TIP] 在 Ire Aderinokun 的文章中了解更多关于此模式样式化的信息。
全局使用 rem,局部使用 em
在根元素设置基础字体大小(html { font-size: 100%; })后,为文本元素设置 em 字体大小:
h2 {
font-size: 2em;
}
p {
font-size: 1em;
}
然后为模块设置 rem 字体大小:
article {
font-size: 1.25rem;
}
aside .module {
font-size: 0.9rem;
}
现在每个模块都变得独立,更易于样式化、更易于维护且更灵活。
隐藏未静音的自动播放视频
这是一个用于自定义用户样式表的绝佳技巧。避免在页面加载时用自动播放视频的声音让用户不堪重负。如果声音未静音,则不显示视频:
video[autoplay]:not([muted]) {
display: none;
}
我们再次利用了 :not() 伪类。
使用 :root 实现灵活排版
响应式布局中的字体大小应该能够随每个视口进行调整。你可以使用 :root 根据视口高度和宽度计算字体大小:
:root {
font-size: calc(1vw + 1vh + 0.5vmin);
}
现在你可以使用基于 :root 计算值的 root em 单位:
body {
font: 1rem/1.6 sans-serif;
}
演示
为更好的移动端体验设置表单元素的 font-size
为了避免移动浏览器(iOS Safari 等)在点击 <select> 下拉菜单时放大 HTML 表单元素,向选择器规则中添加 font-size:
input[type="text"],
input[type="number"],
select,
textarea {
font-size: 16px;
}
使用 Pointer Events 控制鼠标事件
Pointer events 控制指针输入如何命中元素。要阻止在看起来禁用的控件上的点击:
button:disabled {
opacity: 0.5;
pointer-events: none;
}
[!WARNING]
pointer-events: none会阻止指针交互,但不会阻止所有交互。带有pointer-events: none的元素仍然可以接收……
……[截断]