基本操作

我们先写一个简单的HTML,使用 divp标签。再给它们随便加 classclass可以完全自定义,多个class之间空格分隔,但最好整个项目保持一定的命名逻辑

<div class="foo">
  <p class="bar">p1</p>
  <p class="bar">p2</p>
  <p class="bar">p3</p>
</div>

对以上的html,可以有这样的CSS,实现当鼠标移动到 div第二个子元素(也就是第二个 p)时,背景色变为高亮

div.foo>:nth-child(2):hover {
  background-color: aqua;
}

使用CSS选择器 div.foo>:nth-child(2):hover选中元素,再为元素设置 background-color: aqua;等属性,这就是CSS工作的基本模式。接下来我们逐一来看

常用选择器

CSS选择器用简便的符号选择HTML标签的各种属性。最常用的选择器有

  • id选择:#id,这是优先级最高的选择器
  • tag选择:table
  • class选择:.class
  • 存在属性选择:div[type]
  • 属性值选择:div[type="a"]
  • 伪类:以 :开头,用于选择元素的特定状态或位置
    • :hover,:active,:focus,:checked,:disabled:鼠标悬停时,按下鼠标时,正在输入时,勾选时,禁用时
    • :first-child,:nth-child(n),:last-child,选中第n个子元素。div>p:nth-chind(2)表示选中 div的第二个子元素,且该子元素的tag必须是 p
    • :nth-of-type(n),选中第n个子元素。div>p:nth-of-type(2),表示选中 div的第二个tag为 p的子元素,可能在 div的所有子元素中位次不是第二
    • :has(selector),包含某种元素的父元素,如 div:has(p.bar)选中包含 <p class="bar">div
  • 伪元素:以 ::开头
    • ::before,::after:元素之前或之后插入,默认插入inline元素
    • ::first-line,::first-letter:元素的第一行或第一个字符
    • ::selection:鼠标选取的部分

在选择某元素时,不同的选择器可以组合,不分隔代表同级属性,空格代表子孙节点,>分隔代表子节点,+代表紧邻兄弟节点,~代表兄弟节点

两个元素的选择器用 ,分隔,可以统一设置样式

选择器也支持嵌套书写,使用 &代表父选择器

div {
    border: 1px solid #000;
    & h3 {color: red; }
}

更多选择器细节可参考这里

盒模型

在介绍常用属性之前,我们需要了解到,浏览器在解析元素占用的空间时,将每个元素都视为一个盒子(box),也称容器

盒模型包括margin(外距),border(边框),padding(内距),content(内容),只有content是我们在HTML中写下的元素/节点

image.png|500

使用传统盒模型时,属性 width,height指的是content尺寸,这会与视觉尺寸产生误差。解决方式是切换为border-box,使 width,height指content + padding + border之和

全局设定broder-box的CSS是

html {
  box-sizing: border-box;
}
*, *::before, *::after {
  box-sizing: inherit;
}

尺寸属性

  • 常用的尺寸值有pt(像素)、em(相对当前节点字号)、rem(相对根节点字号)、vw(1%的窗口宽度)、dvw(考虑ui变化后vw的更新版)、n%(父节点的比例)
  • 常用的属性有 width,height,padding,broder,margin,font-size等,以及各自的衍生属性
  • 当可以顺次设置四个尺寸时,遵循TRBL (Top、Right、Bottom 和 Left ) 模式
  • aspect-ratio 属性可指定元素的长宽比,这也使其在flex布局中可以按比例伸缩
  • min(),max(),minmax(),clamp()函数在设置尺寸时很有用
  • 尽量让一个元素的属性内含于自身,而不被父级容器所影响。因此要避免设置margin,使用padding

颜色属性

  • 常用的颜色值有十六进制色值、rgb()、hsl()、少数颜色名称等
  • color,outline-color,text-decoration-color,分别是文本颜色、文本边框颜色、文本修饰符(如下划线)颜色
  • background-color,border-color,分别是背景、边框颜色

文本属性

  • font-family:字体系列,如 Arial,也就是口语中的字体
  • font-size:字号,也就是字体大小
  • font-weight:加粗,预设有 normal,bold,bolder,lighter,还可用100-900的数字
  • font-style:常选 normal,italic,即正常、斜体
  • text-align:文本对齐,可选 left,right,center,justify,即左、右、居中、两端对齐
  • text-decoration:可选 none无,underline下划线、overline上划线、line-through删除线
  • letter-spacing,word-spacing,line-height:分别设置字符间距、单词间距、行高
  • text-indent,text-transform:分别设置首行缩进、大小写转换

背景属性

  • background-image
    • 使用 url('./bg.png')引用图片
    • 使用 linear-gradient(to bottom right, red 0%, blue 80%)设置线性渐变,颜色后跟的比例指在路径的百分之多少处达到该颜色,可为负值。另有 radial-gradient()径向渐变和 conic-gradient()锥形渐变
    • 同时设定多个背景时,使用 background-blend-mode属性调整混合模式
  • background-sizecover代表图像等比缩放直到覆盖整个容器,比例有差时图片被裁切;contain也是等比缩放,比例有差时会出现重复部分
  • background-position:背景起始位置,最常用的是 background-size: cover组合 background-position: center
  • background-attachment:背景跟随滚动条的行为,fixed代表不跟随,scroll代表跟随
  • background-clip:设置背景的裁剪。常见的用法是设为 text,配合文本颜色 color: transparent,仅文本显示背景色

动画属性

制作动画时,除了以上提到的颜色、尺寸等属性,元素还有与平移、旋转、缩放、扭曲等操作,这些操作都由 transform属性定义

transform属性接收一系列函数,translate(50px,100px), rotate(45deg), scale(2,4), skew(20deg,30deg),或者可用 matrix()函数同时指定这四种操作。此外,也有 translate3d(), rotate3d(), scale3d(), matrix3d()这些三维变换函数

了解这些操作后,我们就可以创建出一系列有差别的状态,接下来就是添加过渡动画了

第一种添加动画的方法是 transition属性,用于元素从默认状态转向事件状态(如鼠标悬浮 :hover)时。可以单独设定每个属性变化的持续时长,也可用 all统一设置

div {
  background-color: aqua;
}

div:hover {
  transform: rotate(45deg);
  background-color: blue;
  transition: all 2s;
}

第二种添加动画的方法是 animation属性,需搭配 @keyframe关键帧声明使用,不需要事件(如鼠标悬浮)触发

.container {
  background-color: aqua;
  animation: trans 5s infinite;
}

@keyframes trans {
  20% {transform: rotate(45deg) scale(3, 4);}
  75% {transform: rotate(90deg) scale(4, 4);}
}

为了让动画更生动,而不是匀速运动,可以设置 transition-timing-functionanimation-timing-function。其中 ease-in-out代表缓入缓出,还可使用 cubic-bezier()函数指定一条贝塞尔曲线

例如,cubic-bezier(0.68, -0.55, 0.27, 1.55)是带回弹、富有动感的曲线。如果需要自定义贝塞尔曲线,可在这里设计

其他

  • text-shadow/box-shadow:设置文本或容器阴影,格式为 offset-x offset-y radius colorradius越大,阴影范围越大,阴影也越不明显
  • border-radius:设置边框圆角

全部CSS属性清单及详细介绍,可参考MDN文档

变量

CSS中的变量和函数可以避免一些重复操作

:root {
  --main-bg-color: brown;
}

div {
  background-color: var(--main-bg-color, green)
}

--variable的形式声明变量,以 var(--variable)的形式读取变量,可以额外传入多个备选值

由于变量声明也必须定义在选择器内部,最佳实践是使用 :root伪类,使得变量可以全局访问

布局(display)

块与内联

块(block)的默认行为是独占一行,宽度为父元素的100%。采用block布局的元素又称块级元素。div, p, h1等默认是block布局

当节点的 display声明为 blocklist-itemtableflexgrid时,该节点会被标记为块级元素

块级元素的水平、垂直对齐,可借助接下来讲到的flex布局、grid布局完成

内联(inline)的默认行为是不独占行,不可定义宽度(由内容决定),不可定义高度(由字体大小、行距决定),不可定义垂直margin和padding,可以定义水平margin和padding。a, span默认是inline布局

内联元素(如下案例)的垂直对齐,需要使用vertical-align属性,水平对齐使用 text-align属性

<p>Align the star: <img id="example-element" src="/media/examples/star2.png" style="vertical-align: baseline;"></p>

flex布局

通过 display:flexdisplay:inline-flex,可以将当前容器声明为flex容器,它的子元素都将成为flex项目

通过flex布局,我们可以方便地实现对齐、元素的自适应伸缩。这里有demo可以把玩

flex容器在水平面上有主轴、副轴两个方向,默认主轴方向为水平,可通过 flex-direction:column调整

image.png|525

“对齐”这个名词不利于我们理解具体发生了什么。实际上,“对齐”就是当容器内存在剩余空间时,空间的分配方式

justify-content属性用于分配主轴的剩余空间,可选 flex-start,flex-end,center,space-between,space-around,space-evenly(最后三个是分散对齐)。也就是说,在一个flex容器里,主轴方向可能有多个flex项目,该属性可以调整剩余空间如何分配到flex项目的周围

image.png|625

类似地,align-content属性用于分配侧轴的剩余空间。然而,flex是一种一维布局,默认副轴方向只有一行,此时 align-content不起作用。只有指定 flex-wrapwrapwrap-reverse(允许flex项目换行)后,align-content才会生效

div.parent {
  display: flex;
  align-content: center;
  flex-wrap: wrap;
}

结果是,两行flex项目作为一个整体参与剩余空间的分配。如下图设为 center时,两行flex项目之间没有任何间隙

image.png

由于flex是一种一维布局,当flex元素换行后,可以认为是新建了一个主轴。因此,还有一种做法是在两行内部各自分配剩余空间,通过 align-items实现

div.parent {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

如下图设为 center时,两行flex项目各自做了垂直居中

image.png|700

也可以只对某个item项目做调整,使用 align-self

div.parent {
  display: flex;
  flex-wrap: wrap;
}
p.child {
  align-self: center;
}

你可能发现了,假如flex布局只有一行,flex容器属性 flex-wrap + align-contentalign-items,或flex项目属性 align-self,完全可以达到相同的效果

flex项目可以自适应伸缩,以适应不同的flex容器尺寸

  • flex-grow:先按照所有子项的 width/height计算flex容器的剩余空间,如有,则flex项目按一定的配比分配剩余空间。该参数越大,获得的配比越高
  • flex-shrink:同上,在空间不足时的缩小配比

当然,这些伸缩也都服从 min-width,max-width属性的限制,常与 max-content,min-content,fit-content搭配使用

如果你希望设定flex项目的间距,相比于传统的margin和padding,flex容器可以设定更好用的 column-gap/row-gap参数

希望对少数几个flex项目重新排序的话,可使用 order属性

grid布局

grid布局是唯一的二维布局方案,完全改变了我们设计用户界面的方式。目前的最佳实践是,用grid布局操作整个页面、复杂组件,用flex布局操作简单组件

使用 display:grid声明一个grid容器,内部所有元素都成为grid项目。grid容器和表格比较类似,由行(row)和列(column)组成,行与列交叉处即为单元格,单个或多个相邻单元格构成grid区域(area),后续就将grid项目放置在grid区域中

我们首先要定义grid容器的区域,最推荐的做法是在 grid-template-areas中使用语义化的字符串,在 grid-template-columns/rows中设定空间尺寸,随后即可在grid项目中分配

.container {
    grid-template-areas:
        "header  header  header"
        "nav     main    aside"
        "footer  footer  footer";
	grid-template-columns: 1fr 2fr 1fr;
	grid-template-rows: 1fr 2fr 1fr;
}
.header {
	grid-area: header
}

fr是grid布局特有的长度单位,类似flex布局中的伸缩系数,决定了除去内容所占的空间外,剩余空间的分配。这也就意味着将三列设为 grid-template-columns: 1fr 1fr 1fr,并不能保证三列是等宽的,因为某列内容所占的空间可能更大

如果需要三列严格等宽,可以使用 grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr)。这时有可能遇到内容溢出,需要对应的处理

flex-grow/flex-shrink的区别是,fr单位只设置了grid布局,不会导致grid项目元素的伸缩

当然,也有通过网格线分配的做法,网格线索引从1开始,反向从-1开始。使用 grid-template-areas后,也会自动创建名为 areaname-start-end的网格线。在此不做介绍

调整grid布局的对齐,除了flex布局已有的 align-系列属性,水平方向也有了完整的 justify-content,justify-items,justify-self三大属性

justify-content作为grid容器属性,用于在整个grid容器中,分配水平方向的剩余空间

image.png|650

justify-items作为grid容器属性,用于每个grid项目内,水平方向剩余空间的分配

image.png|650

justify-self作为grid项目属性,设置当前grid项目内,水平方向剩余空间的分配

总结下来,无论flex布局还是grid布局,以 justify开头是设置水平方向,以 align开头是设置垂直方向;以 content结尾是分配容器剩余空间,以 items结尾是分配子项内剩余空间,以 self结尾是子项的属性,单独分配当前子项剩余空间

如果要设置grid区域的间隙,在grid布局中也可使用 column-gap/row-gap参数

需要注意的是,在grid区域放置元素时,如果该元素的原始尺寸较大、无法放入(例如尺寸较大的图片),则会自动后移,直到找到可以容纳的grid区域,这就产生了网格缺口。为避免这种现象,可以设置grid容器属性 grid-auto-flow: dense

定位(position)与浮动(float)

float属性曾广泛用于页面布局,但在flex布局、grid布局出现后,float回归了它原本的作用——设置文字环绕,参考这里

position属性默认是 static,该属性的 fixed,absolute选项用于放置不随页面滚动(或称脱离正常文档流)的元素。fixed将元素根据屏幕视口固定,常用于导航栏;absolute将元素根据非 static的父元素固定,常用于“返回顶部”按钮、悬浮菜单。更详细的描述参考这里

如果你想要查看网站布局的发展,可以在这里查看历史上知名的网站

响应式布局

响应式布局,是指页面尺寸或容器尺寸发生变化时,页面元素自动切换布局方案的技术,主要通过媒体查询 @media和容器查询 @container实现

该技术的主要卖点是,再也不需要维护移动端、平板端、PC端三套布局了!一个布局加响应式设计全部搞定

虽然听起来很诱人,但远远没在生产环境普及。原因是响应式布局的设计、代码编写费时费力,很多时候还不如写两套布局

响应式布局的语法并不难,难的是在HTML固定的情况下,如何让元素优雅地遵从多套布局方案——这是需要长期实践积累感觉的。感兴趣的话可查看这篇文章

溢出处理

文本过长导致的溢出,可以使文本显示为一行,溢出部分隐藏,并在行尾添加省略号

p {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

也可以保持文本多行显示,为文本区域添加滚动条

p {
  overflow-y: auto;
}

如果在Chrome、Safari、Edge、大部分安卓浏览器,还可以方便地设置文本显示的行数,溢出部分隐藏,并在行尾添加省略号

p {
  overflow : hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}

对于图片溢出,最简单的是设置 max-width: 100% 。图片具有自身尺寸,默认行为会和其他元素不同max-系列属性可以在自身尺寸过大时等比例缩放。如果不需要等比例缩放,可使用object-fit

对于其他常规元素,一些特殊值可帮助避免溢出现象

  • min-content :不导致溢出时,内容的最小尺寸。对于一个英文句子,是最长单词的宽度
  • max-content :当父级容器无限大时,内容的尺寸。对于一个英文句子,是平铺在一行的宽度。这非常容易导致溢出
  • fit-content :父级容器最大的可用宽度。对于一个英文句子,是撑满父级容器、换行之后的宽度

常用的是将容器的 width设为 fit-content

tailwindCSS

tailwindCSS使用原子化、内含样式的类名,极大减少了对CSS的维护,增加了组件的内聚性,也更方便复用。更多设计哲学请参考这篇博文,在线游玩点击这里

大多数属性对应的类名都很直观,如 w-,h-对应 width,heightp-,m-对应 padding,margin,参考官方文档即可。VScode也有tailwindCSS的插件,辅助代码编写

行内元素的对齐,text-对应 text-alignalign-对应 vertical-align

块级元素的对齐,justify-对应 justify-contentjustify-items-对应 justify-itemscontent-对应 align-contentitems-对应 align-items

flex布局使用 flex声明,flex项目的 grow等同于 flex-grow,设置 flex-grow: 1,如果需要设置别的系数,类名使用 flex-grow-[n]

<div class="flex h-20 items-center justify-around gap-x-1 bg-slate-400 text-center">
  <div class="flex-grow-[2] bg-yellow-300">d1</div>
  <div class="bg-blue-400">d2</div>
  <div class="grow bg-green-300">d3</div>
  <div class="bg-red-500">d4</div>
</div>

这产生如下的布局

image.png|700

grid布局使用 grid grid-cols-3 grid-rows-2声明,通过 row-span,col-span系列类分配grid area

<div class="grid grid-cols-3 grid-rows-2 items-center gap-1 bg-slate-400 text-center">
  <div class="row-span-2 bg-yellow-300">d1</div>
  <div class="col-span-2 bg-blue-400">d2</div>
  <div class="bg-green-300">d3</div>
  <div class="bg-red-500">d4</div>
</div>

这产生如下的布局

image.png

子项间距有 gap-space-两种方案,前者基于CSS的 gap属性,后者基于 margin属性。没有特殊需求都应该使用 gap-

可以使用 @apply指令封装tailwindCSS class,支持与css联用

.bgr {
  @apply h-20 w-20 bg-red-500;
  border-radius: 25%;
}

<div class="bgr">hello</div>

由于apply的实现过于复杂,维护成本很高,tailwindCSS的作者曾经呼吁社区使用语法更复杂、但实现更简单的 theme(),比如这个推文这个推文,也给出了 @apply导致bug的案例,可以当做八卦了解一下。对于我们使用者,确保 @apply引用的class独立定义过,在复杂的选择器场景下仔细测试即可

对于伪类、伪元素、媒体查询,tailwindCSS也给了直接的简化。例如 hover:bg-sky-700after:content-['*'] after:ml-0.5 after:text-red-500sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4。详见这里

对于动画,tailwindCSS也给出了一系列常用的 animate-预设,详见这里

经过以上的介绍,相信你对tailwindCSS已经有所了解。UnoCSS在tailwindCSS的基础上,又优化了许多操作,具体可以看这篇文章。有兴趣可以尝试

最佳实践

在初始化项目后,去除丑陋的元素默认样式(例如 <a>的下划线、<input>的边框),因为我们总是会自定义的。流行的方式是The New CSS Reset,通过 .css或npm包的形式重置样式

先思考清楚页面的功能分区,划分好组件,然后用grid布局操作整个页面或复杂的组件,用flex布局操作简单的组件

设置尺寸时,rem > em > px,dvw/百分比 > vw;设置间距时,优先级gap > padding > margin

大部分的样式应当由tailwindCSS的class完成,可重用的样式要适当地 @apply封装

如有余力,用媒体查询或容器查询实现响应式布局——这个过程可能非常繁琐。虽然大家都在说“移动端优先”,意思是布局优先适配移动端,然后通过响应式布局适配桌面端,但其实为移动端、桌面端开发两套前端的情况非常常见(而且成本可能更低)

如果你想获得更多CSS实例、教程,可以到CSS-TricksCodePen Spark