入门CSS
基本操作
我们先写一个简单的HTML,使用 div
和 p
标签。再给它们随便加 class
。class
可以完全自定义,多个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中写下的元素/节点
使用传统盒模型时,属性 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-size
:cover
代表图像等比缩放直到覆盖整个容器,比例有差时图片被裁切;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-function
或 animation-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 color
,radius
越大,阴影范围越大,阴影也越不明显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
声明为 block
、list-item
、table
、flex
或 grid
时,该节点会被标记为块级元素
块级元素的水平、垂直对齐,可借助接下来讲到的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:flex
或 display:inline-flex
,可以将当前容器声明为flex容器,它的子元素都将成为flex项目
通过flex布局,我们可以方便地实现对齐、元素的自适应伸缩。这里有demo可以把玩
flex容器在水平面上有主轴、副轴两个方向,默认主轴方向为水平,可通过 flex-direction:column
调整
“对齐”这个名词不利于我们理解具体发生了什么。实际上,“对齐”就是当容器内存在剩余空间时,空间的分配方式
justify-content
属性用于分配主轴的剩余空间,可选 flex-start,flex-end,center,space-between,space-around,space-evenly
(最后三个是分散对齐)。也就是说,在一个flex容器里,主轴方向可能有多个flex项目,该属性可以调整剩余空间如何分配到flex项目的周围
类似地,align-content
属性用于分配侧轴的剩余空间。然而,flex是一种一维布局,默认副轴方向只有一行,此时 align-content
不起作用。只有指定 flex-wrap
为 wrap
或 wrap-reverse
(允许flex项目换行)后,align-content
才会生效
div.parent {
display: flex;
align-content: center;
flex-wrap: wrap;
}
结果是,两行flex项目作为一个整体参与剩余空间的分配。如下图设为 center
时,两行flex项目之间没有任何间隙
由于flex是一种一维布局,当flex元素换行后,可以认为是新建了一个主轴。因此,还有一种做法是在两行内部各自分配剩余空间,通过 align-items
实现
div.parent {
display: flex;
align-items: center;
flex-wrap: wrap;
}
如下图设为 center
时,两行flex项目各自做了垂直居中
也可以只对某个item项目做调整,使用 align-self
div.parent {
display: flex;
flex-wrap: wrap;
}
p.child {
align-self: center;
}
你可能发现了,假如flex布局只有一行,flex容器属性 flex-wrap + align-content
、align-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容器中,分配水平方向的剩余空间
justify-items
作为grid容器属性,用于每个grid项目内,水平方向剩余空间的分配
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,height
,p-,m-
对应 padding,margin
,参考官方文档即可。VScode也有tailwindCSS的插件,辅助代码编写
行内元素的对齐,text-
对应 text-align
,align-
对应 vertical-align
块级元素的对齐,justify-
对应 justify-content
,justify-items-
对应 justify-items
,content-
对应 align-content
,items-
对应 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>
这产生如下的布局
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>
这产生如下的布局
子项间距有 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-700
,after:content-['*'] after:ml-0.5 after:text-red-500
,sm: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-Tricks或CodePen Spark